Getting the feet wet with DKIM

DKIM stands for DomainKeys Identified Mail, it’s an anti-phishing / anti-spoofing system for email that relies on private/public key system to authenticate the origin of the email. Kind of SPF records but on steroids. You can find a pretty concise explanation of the DKIM system here.

I needed to figure this out, as my SPAM (sorry let me just correct this), my outgoing SOLICITED bulk email sending box was getting lower deliverability rates. So it seems having this system helps, because as everybody knows all the lower grade spam senders can’t figure this out.

First thing is to create a private/public key pair. You can go all out with OPENSSL, but as I feel a bit lazy, and not in the mood for man pages and the macho man CLI black magic, just used the online tool that the good people of Worx (the PHPMailer people) provide here.

You must provide the filenames for the pair, just click next, and in the next step fill the domain (pretty self explanatory), the identity (just left blank), the selector, and a passphrase (I left that blank also). The selector is mandatory, as the DKIM system allows to have several keys per domain, it uses the selector to identify which key the email was signed with. Even if you are using just one key per domain you need to assign a value here (I used “default”, but it can be anything like “potato” or “balls”.

You will get a zip file with everything you need, and even some instructions to protect the key files with .htaccess directives, because as the good people of Worx know, PHPMailer is PHP, and all PHP programmers are dummies, so they will happily put the key files under the domain website root and probably with a couple of links from the website homepage.

I bet that somewhere in the support forums somebody asked: why I can’t access my key files, always getting those 403 Forbidden errors.

First thing is to publish your public key on DNS. I use DJB tinydns DNS server with VegaDNS management, the old PHP version when the author placed key files in the public website root. Now he is smart, the 2.0 version is in Python and he doesn’t do this anymore… anyway add a new TXT record for the domain, in the hostname put “selector._domainkey” (change the selector part to the string you defined before), and in the address field put in one line only:

v=DKIM1; k=rsa; p=publickey

(replace publickey with the public key that you get previously). If you are using other DNS server check out the specific documentation on creating TXT records.

Now, to sign the email messages you send. Obviously the natural choice is to have your MTA auto magically sign the outgoing emails. To me that means to mess with my power beast Qmail production installation, this means messing with the macho man stuff, patches, libraries and compilers. Not so good to make a quick fix for sending SPAM (sorry, SOLICITED bulk email). So I went the easy route and use the PHPMailer to sign the outgoing emails.

Just have to add a couple of lines:

$email = new PHPMailer();
$email->DKIM_domain = 'domain.com'; // the domain
$email->DKIM_private = '/path/to/private/key'; // please even using PHP keep the file above the webroot
$email->DKIM_selector = 'default'; // the selector you published in DNS

and proceed to testing. There are plenty of online services that provide us this service. I liked this one. But after doing some testing I quickly realized that something was not well in the DKIM land…

All the outgoing emails were sent with some bad header char. Even sending to my own system, the Amavisd was quarantining the emails with virus suspicions due to a bad header. Further looking into this, just realized that between the last header line and the DKIM signature there was a dreadful ^M (a carriage return, the DOS \n\r not compliant with the email spec). This is in a plain vanilla Qmail running on Debian, and with latest stable PHPMailer.

There was just one thing to do, bring out the old quick and fix hammer and change the class.phpmailer.php file. If you have the same problem in line 1305 change the CRLF to just LF:

/*
$this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
    str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
*/
$this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . "\n" .
    str_replace("\r\n", "\n", $header_dkim) . self::CRLF;

And everything is running nice and well.