DKIM conformance example


Having had to get to grips with various forms of cryptography in recent years a common theme is that those who create libraries or standards rarely if ever publish conformance data. It seems the developers/specifiers think their spec is so straight forward/complete anyone can follow along. I beg to differ. My first experience was with the encryption in Office 2007. Microsoft’s David LeBlanc published the specifications of the algorithms used – except there was an error. David kindly fixed the error and then also published worked examples on Codeplex.

So it’s DKIM this time. Now DKIM isn’t difficult but, like all asymmetric encryption, it’s opaque because you can’t just decrypt and there’s no example data on which to cut your teeth. So this post is to fill a gap. There’s no code here, just examples of what you should see at steps during the signing process if your code takes the example email and signs it.
Limitation of this example

This example works flawlessly for me but will not not for you. Even if your code produces exactly the same output as that shown here it will not result in an email which a third party will verify. Why? Because the example assumes you are sending from my email address. A verifier will look to my DNS server for the public key to use to the verify the signatue shown here but the public key will be different.

Even so, the failure occurs on verification. Your code should be able to use the information here and produce the same signature. If it does, you should then be able to use your own email address and keys and successfully send a signed email.

Relax
The example shown here is using the DKIM ‘relaxed’ encoding for headers and the body because it’s the more complicated. In the DKIM spec ‘relaxed’ means that every group of whitespace and folding whitespace in headers which are to be made part of the signature and the body is replaced by a single space. Though the spec identifies a case when the relaxed encoding may result in an email being abused, using the relaxed option means its more likely the signature will remain valid after passing through the mail servers on route. If it’s likely a signature will be invalid by the time it reaches the destination anyway there’s no point signing it so the slight risk of using the relaxed encoding seems reasonable.

End of line markers
In the text shown, if there’s a <crlf> at the end of a line it means there’s really an end-of-line marker at that point. The character sequence <crlf> doesn’t really exist in the email and it’s there to show where the real end-of-line marker will appear even if the text shown is wrapped by your browser. This is because handling lines correctly is important in the DKIM signing process.

The original email
Here’s the simple email to be sent:

1
2
3
4
5
6
7
8
9
10
11
12
Return-Path: aws@s3ig.com<crlf>
MIME-Version: 1.0<crlf>
From: aws@s3ig.com<crlf>
To: check-auth@verifier.port25.com<crlf>
Reply-To: aws@s3ig.com<crlf>
Date: 10 Mar 2011 10:41:56 +0000<crlf>
Subject: dkim test email<crlf>
Content-Type: text/plain; charset=us-ascii<crlf>
Content-Transfer-Encoding: quoted-printable<crlf>
<crlf>
This is the body of &tab; the message.=0D=0AThis is the second line<crlf>
<crlf>
Return-Path: aws@s3ig.com<crlf>
MIME-Version: 1.0<crlf>
From: aws@s3ig.com<crlf>
To: check-auth@verifier.port25.com<crlf>
Reply-To: aws@s3ig.com<crlf>
Date: 10 Mar 2011 10:41:56 +0000<crlf>
Subject: dkim test email<crlf>
Content-Type: text/plain; charset=us-ascii<crlf>
Content-Transfer-Encoding: quoted-printable<crlf>
<crlf>
This is the body of &tab; the message.=0D=0AThis is the second line<crlf>
<crlf>

This email is going from our aws@s3ig.com account to the email signing test address at port25.com

Public and Private keys
To sign an email you need a private key and to verify the signature the person verifying will need the corresponding public key. These are the keys used in this example. As you will see they are in a .pem format:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-----BEGIN RSA PRIVATE KEY-----<crlf>
MIICXQIBAAKBgQDptLAWO6YJyVGKwayFzVCEIhY0bjE4ObCr7JeILVWi9ELaWwdm<crlf>
PEKW0afsqB/jR+yhMIdeyGbW9li4P2X8oFDLFwAkAng857ngz2RYNjf+7bGgvH8n<crlf>
gbKXuFtnO1kI5+ou3C1+Tixk8aY44uWCFamXueAW1ZzlzexViyG4gdVXlQIBEQKB<crlf>
gQDb9VpvRzLcCMU3TN6cDIgD49ip0R9D+g+w3qy8Zucv9POgVaycdPNgxVLAnjwh<crlf>
NKJ5lxX+2rskq57LhvaTabVyDNkRjp8Oks1+nCbklaCEdkJ0S9z/IqjmmqCY4bP1<crlf>
kJ0Eu18NHBId8dXoK3EQ8nzRCKE+tjkiMl5jC1kJzVdg8QJBAPrDkKMURlLRnMEA<crlf>
LzXHwoNAPdK94IFwdypq62yDVm4u2rSMUtYlKmJQFiSkytQ2vjBb5HRxqL+p4mnq<crlf>
30ZHWdUCQQDulfC32vcY7e2IetYhda+sysdZJnfrbquJpdlfBn2QFH8gjC2KM/q+<crlf>
YtwQGJU/zjtwWN+/joi4vinlKD7RYSbBAkEAk4IY2GZHfALUrcPfiQwYEPic1lGT<crlf>
Hvbcr4owIbarT99TeUN8BX9GG7ajnRWkfNToWK6GYp02FmPumKhHGkgWuQJBAKhp<crlf>
1xheVBGY4+fePMxTEpgWqtWEkOJsPNmiPxXmdsAOd9q9TVJ/C1k2uXTGDv/c3qmo<crlf>
JXgoYIJoHZKy/ypisfECQQC9FxTwEfzFLTANQVnUQKEbRUk3slaigQb3QoBKXOZr<crlf>
oCyn+rxDcflW1RbZnsilaMbpN/PMw/IbqRjXA2Tg3Ty6<crlf>
-----END RSA PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----<crlf>
MIICXQIBAAKBgQDptLAWO6YJyVGKwayFzVCEIhY0bjE4ObCr7JeILVWi9ELaWwdm<crlf>
PEKW0afsqB/jR+yhMIdeyGbW9li4P2X8oFDLFwAkAng857ngz2RYNjf+7bGgvH8n<crlf>
gbKXuFtnO1kI5+ou3C1+Tixk8aY44uWCFamXueAW1ZzlzexViyG4gdVXlQIBEQKB<crlf>
gQDb9VpvRzLcCMU3TN6cDIgD49ip0R9D+g+w3qy8Zucv9POgVaycdPNgxVLAnjwh<crlf>
NKJ5lxX+2rskq57LhvaTabVyDNkRjp8Oks1+nCbklaCEdkJ0S9z/IqjmmqCY4bP1<crlf>
kJ0Eu18NHBId8dXoK3EQ8nzRCKE+tjkiMl5jC1kJzVdg8QJBAPrDkKMURlLRnMEA<crlf>
LzXHwoNAPdK94IFwdypq62yDVm4u2rSMUtYlKmJQFiSkytQ2vjBb5HRxqL+p4mnq<crlf>
30ZHWdUCQQDulfC32vcY7e2IetYhda+sysdZJnfrbquJpdlfBn2QFH8gjC2KM/q+<crlf>
YtwQGJU/zjtwWN+/joi4vinlKD7RYSbBAkEAk4IY2GZHfALUrcPfiQwYEPic1lGT<crlf>
Hvbcr4owIbarT99TeUN8BX9GG7ajnRWkfNToWK6GYp02FmPumKhHGkgWuQJBAKhp<crlf>
1xheVBGY4+fePMxTEpgWqtWEkOJsPNmiPxXmdsAOd9q9TVJ/C1k2uXTGDv/c3qmo<crlf>
JXgoYIJoHZKy/ypisfECQQC9FxTwEfzFLTANQVnUQKEbRUk3slaigQb3QoBKXOZr<crlf>
oCyn+rxDcflW1RbZnsilaMbpN/PMw/IbqRjXA2Tg3Ty6<crlf>
-----END RSA PRIVATE KEY-----
1
2
3
4
5
6
-----BEGIN PUBLIC KEY-----<crlf>
MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDptLAWO6YJyVGKwayFzVCEIhY0<crlf>
bjE4ObCr7JeILVWi9ELaWwdmPEKW0afsqB/jR+yhMIdeyGbW9li4P2X8oFDLFwAk<crlf>
Ang857ngz2RYNjf+7bGgvH8ngbKXuFtnO1kI5+ou3C1+Tixk8aY44uWCFamXueAW<crlf>
1ZzlzexViyG4gdVXlQIBEQ==<crlf>
-----END PUBLIC KEY-----
-----BEGIN PUBLIC KEY-----<crlf>
MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDptLAWO6YJyVGKwayFzVCEIhY0<crlf>
bjE4ObCr7JeILVWi9ELaWwdmPEKW0afsqB/jR+yhMIdeyGbW9li4P2X8oFDLFwAk<crlf>
Ang857ngz2RYNjf+7bGgvH8ngbKXuFtnO1kI5+ou3C1+Tixk8aY44uWCFamXueAW<crlf>
1ZzlzexViyG4gdVXlQIBEQ==<crlf>
-----END PUBLIC KEY-----

Hashed body
The start point is to hash a canonicalized form of the body. Canonicalizing the email body results in:

1
This is the body of the message.=0D=0AThis is the second line<crlf>
This is the body of the message.=0D=0AThis is the second line<crlf>

The example uses the SHA256 algoithm which produces this hash:

1
vrfP/4tQvd9QIewLlBjIlqsKMPwXXKj66neZg/smWSc=
vrfP/4tQvd9QIewLlBjIlqsKMPwXXKj66neZg/smWSc=

The DKIM tags
The hash of the body can be used to create the basic DKIM tag string. In accordance with the spec, this tag string will be used in a couple of the next steps.

1
2
3
v=1; a=rsa-sha256; c=relaxed/relaxed; d=s3ig.com; q=dns/txt; s=dkim; 
t=1299753716; bh=vrfP/4tQvd9QIewLlBjIlqsKMPwXXKj66neZg/smWSc=; 
h=Content-Type:From:Subject:To; b=
v=1; a=rsa-sha256; c=relaxed/relaxed; d=s3ig.com; q=dns/txt; s=dkim; 
t=1299753716; bh=vrfP/4tQvd9QIewLlBjIlqsKMPwXXKj66neZg/smWSc=; 
h=Content-Type:From:Subject:To; b=

This is one long line and the “b=” at the end is left dangling on purpose – it part of the specification. The ‘c’ tag documents to the verifier that the headers/body will use the relaxed encoding. This email is from our s3ig.com domain and the public key can be found by reference to the ‘dkim’ sub-domain. That is, the public key can be found in the sub-domain dkim._domainkey.s3ig.com.

Canonicalilzed headers
In the example, the Content-Type, From, Subject and To headers are to be included in the signature. After the headers have been canonicalized, they are concatenated and the DKIM header and DKIM tag string from above is appended to create the string to be signed.

Remember, the dkim-header included is long and will wrap in your browser but is really one line. End of line markers show where line ends really occur.

1
2
3
4
5
6
7
8
content-type:text/plain; charset=us-ascii<crlf>
from:aws@s3ig.com<crlf>
subject:dkim test email<crlf>
to:check-auth@verifier.port25.com<crlf>
dkim-signature:v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=s3ig.com; q=dns/txt; s=dkim; t=1299753716;
 bh=vrfP/4tQvd9QIewLlBjIlqsKMPwXXKj66neZg/smWSc=;
 h=Content-Type:From:Subject:To; b=
content-type:text/plain; charset=us-ascii<crlf>
from:aws@s3ig.com<crlf>
subject:dkim test email<crlf>
to:check-auth@verifier.port25.com<crlf>
dkim-signature:v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=s3ig.com; q=dns/txt; s=dkim; t=1299753716;
 bh=vrfP/4tQvd9QIewLlBjIlqsKMPwXXKj66neZg/smWSc=;
 h=Content-Type:From:Subject:To; b=

Signing
The signature is generated using SHA256 hashing and RSA encryption using the private key above. I use the BouncyCastle library and specify SHA256WithRSAEncryption when creating the signer. This algorithm is included in the text to be signed (a=rsa-sha256) so the verifier also knows what to use.

After Base64 encoding the generated signature you should see:

1
2
3
4
enIert1AWY8K9AIxTw0qQLOO3TKuRENfJvwYWDXi6xM7IWaz+B
b83xi5YnjBH0Q8opLn643qIaXGVIU2+LBA2a44PZGtTRXYMG3sbQpcEM
jfJRPAhAQOazsSlVdq4SmAChAU3g8uPj4r71JdROucZSdm/mW8IoT4Iy
mpoCiLKdQ=
enIert1AWY8K9AIxTw0qQLOO3TKuRENfJvwYWDXi6xM7IWaz+B
b83xi5YnjBH0Q8opLn643qIaXGVIU2+LBA2a44PZGtTRXYMG3sbQpcEM
jfJRPAhAQOazsSlVdq4SmAChAU3g8uPj4r71JdROucZSdm/mW8IoT4Iy
mpoCiLKdQ=

The DKIM header
Finally, the DKIM signing header can be generated and added to the email. The final result is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MIME-Version: 1.0<crlf>
From: aws@s3ig.com<crlf>
To: check-auth@verifier.port25.com<crlf>
Reply-To: aws@s3ig.com<crlf>
Date: 10 Mar 2011 10:41:56 +0000<crlf>
Subject: dkim test email<crlf>
Content-Type: text/plain; charset=us-ascii<crlf>
Content-Transfer-Encoding: quoted-printable<crlf>
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=s3ig.com; q=dns/txt; s=dkim;
 t=1299753716; bh=vrfP/4tQvd9QIewLlBjIlqsKMPwXXKj66neZg/smWSc=; 
 h=Content-Type:From:Subject:To;
 b=enIert1AWY8K9AIxTw0qQLOO3TKuRENfJvwYWDXi6xM7IWaz+Bb83xi5
YnjBH0Q8opLn643qIaXGVIU2+LBA2a44PZGtTRXYMG3sbQpcEMjfJRPAhAQ
OazsSlVdq4SmAChAU3g8uPj4r71JdROucZSdm/mW8IoT4IympoCiLKdQ=<crlf>
<crlf>
This is the body of &tab; the message.=0D=0AThis is the second line<crlf>
<crlf>
MIME-Version: 1.0<crlf>
From: aws@s3ig.com<crlf>
To: check-auth@verifier.port25.com<crlf>
Reply-To: aws@s3ig.com<crlf>
Date: 10 Mar 2011 10:41:56 +0000<crlf>
Subject: dkim test email<crlf>
Content-Type: text/plain; charset=us-ascii<crlf>
Content-Transfer-Encoding: quoted-printable<crlf>
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=s3ig.com; q=dns/txt; s=dkim;
 t=1299753716; bh=vrfP/4tQvd9QIewLlBjIlqsKMPwXXKj66neZg/smWSc=; 
 h=Content-Type:From:Subject:To;
 b=enIert1AWY8K9AIxTw0qQLOO3TKuRENfJvwYWDXi6xM7IWaz+Bb83xi5
YnjBH0Q8opLn643qIaXGVIU2+LBA2a44PZGtTRXYMG3sbQpcEMjfJRPAhAQ
OazsSlVdq4SmAChAU3g8uPj4r71JdROucZSdm/mW8IoT4IympoCiLKdQ=<crlf>
<crlf>
This is the body of &tab; the message.=0D=0AThis is the second line<crlf>
<crlf>

Information and Links

Join the fray by commenting, tracking what others have to say, or linking to it from your blog.


Other Posts

Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

Thank goodness SOMEBODY thought to publish some test vectors! Thanks dude!
(One small comment though: I think something has gone awry with the LT tags in the private key – a cut and waste error perhaps ;-).

Thanks for the hint. Now fixed for others.

You can also send an email to “mailtest@unlocktheinbox.com”. It really helps break down the DKIM headers and tells you specifically what’s wrong and provides you information on how to correct it.

Henry, the information returned by the service is similar to that provided by Port25.

What I’m providing in this post is information useful to someone creating a DKIM signing routine. That is, the intermediary values required on the way to creating the final signature and which is not available via verifiers like Port25.

First of all: THANK YOU! You don’t realize how rare it is to see someone actually provide an example, let alone a well explained example of DKIM.

Anyway, after reading the RFC specs i got the impression one is to c14n the spaces and “;” of all the headers to be signed…so for example in your example: Date: 10 Mar 2011 10:41:56 +0000
Would now be:
date:10=20Mar=202011=2010:41:56=20+0000

Am i missing something? I too use “relax/relax” and so wasnt sure what is correct. So far doing experiments i am unable to verify signed emails regardless so am ripping my hair out trying to figure this stuff out. I’d love for you to email so we can discuss further.

Cheers

Perhaps the mistake you are making is with the headers. The headers themselves remain with their normal layout (except that white space will have been folded). It is the *names* of the headers that are included as part of the signature are ‘contactenated’ using ‘;’ into a field the DKIM signature called ‘h=’. Only the body is encoded using =OD (and other char replacements). The headers are not. They are simply folded so your example is not correct. relax/relax is the best to use. If relax is not used, an intermediary server can invalidate the signature just by changing the layout of the body. relax is resilient in the face of such changes. I don’t include the date in the signature because, again, an intermediary server could change the date. Exchange is an example of an email server that definitely changes the date on any message passing through.

Thanks for the quick reply “bills”. Interesting stuff…Hey so using relax for headers would this original header (note it going to multi-lines and the space before each line):
Content-Type: text/plain;
charset=ISO-8859-1;
DelSp=”Yes”;
format=”flowed”
Now be look like this just before DKIM hashing/signing:
Content-Type:text/plain; charset=ISO-8859-1; DelSp=”Yes”; format=”flowed”

Is that correct?

Ooops looks like this comment tool isnt preserving my original code:
It should say (replace the {sp} with a white space, and {cr+lf} with u know):
Content-Type: text/plain;{cr+lf}{sp}charset=ISO-8859-1;{cr+lf}
{sp}DelSp=”Yes”;{cr+lf}
{sp}format=”flowed”{cr+lf}

And for DKIM hashing+signing (relax) it should look like this:
Content-Type:text/plain; charset=ISO-8859-1; DelSp=”Yes”; format=”flowed”{cr+lf}

Is that correct?

uggh…forgot to lowercase the last line header to read: “content-type:”

Looks right to me though you need to check with FWS rules in the spec to be sure.

I remember that it took a few times reading the spec to get it.

Ok thanks a bunch bills! You’ve been extremely helpful! This entire blog post is invaluable!
Cheers

Hi bills..i am still pulling my hair out trying to figure why my steps arent working to verify DKIM Signature. I have put together a ZIP file containing my steps and others (my public.key, signed signature file, headers+dkim text to verify, original email) to make it easier for you to spot the problem.

Would you have time to check it out?

I can email it to ya (not too eager to have a link posted here to it). Just email me and i will send it to ya.

Amanda

I’d be reluctant to go down that route. Reading other people’s code is difficult at the best of times especially when looking for specific implementation issues. In this case it’s 18 months since anyone here has had to look at DKIM details so it would entail refamiliarizing ourselves with the DKIM spec in order to spot the problem(s) at the heart of your concerns.

Validation is a degenerate case of signing. Degenerate in that it’s not necessary to go all the way. What you are doing to is attempting to generate the same hash as the sender based on the headers listed in the DKIM header and, of course, the message body. Have you tried to create the same hash shown in this post using the same information? Have you taken a simple minded implementation such as the one here (http://tinisles.blogspot.co.uk/2009/09/sending-dkim-email-from-c.html) and confirmed you are able to generate a message that will be successfully verified by port 25?