Fun with DKIM

DKIM signing and verification look mostly like an opaque black box from the outside, without any easy way to look at intermediate steps and understand what’s going on. Perhaps we can do something, though.

We really can’t do the RSA cryptography by hand, but let’s see how far we can get.

Canonicalization

The first step in DKIM signing a message is to canonicalize and hash the body. Canonicalization is the process of tidying up whitespace, so that trivial changes as the message is sent don’t change the body hash. There are two sorts of canonicalization - simple and relaxed. You’ll see these listed as relaxed/simple and relaxed/relaxed - the “relaxed” before the slash is describing how the headers are canonicalized, which we don’t care about right now.

Simple body canonicalization chops off any trailing blank lines, and if what’s left doesn’t finish with a CRLF, adds a CRLF to the end.

“CRLF” is short for carriage return + line feed, and means the sequence of two bytes that’s used to end lines in email. The bytes are 10 and 13, often written as “\r\n” and universally just called a CRLF.

Relaxed body canonicalization does a bit more. It removes all whitespace at the end of each line, collapses any sequence of whitespace in a line into a single space and removes any trailing blank lines. What’s left is the relaxed canonicalized body.

Both of these are described in RFC 6376, sections 3.4.3 and 3.4.4.

The simplest case for calculating a body hash is an email with an empty body, so let’s do that.

Simple canonicalization will convert it an empty body just a CRLF, two bytes long, while relaxed canonicalization will convert it to an empty string, zero bytes long.

Hashing

Once we have the canonicalized body we need to hash it, creating a fingerprint that will change if any part of the body is changed. To do this we take a SHA256 hash of the (canonicalized) body and convert it to a base64 encoded string.

We can do that like this, for the simple canonicalization of our empty body:

echo -n "\r\n" | openssl sha256 -binary | base64

“echo -n” just takes the string it’s given - our CRLF here - and prints it. The “-n” tells echo not to add a newline, just to print what it’s given. And “\r\n” is the way we type a CRLF into the shell.

“openssl sha256 -binary” takes it’s input, runs it through the SHA256 hashing algorithm and prints the result in binary (i.e. not encoded into hex digits, which would be the format you’d typically see).

“base64” then takes that binary hash and encodes it with base64 encoding. That gives us this, our simple body hash:

frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=

And we can do the same thing for our relaxed canonicalization:

echo -n "" | openssl sha256 -binary | base64

That gives us this, our relaxed body hash for an empty body:

47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=

This body hash will be stored in the bh= field of the DKIM-Signature header of the mail.

Really, that’s how it works

Send yourself an email with an empty body, maybe to your gmail account. Look at the headers of the mail and find the DKIM-Signature header.

It’ll look something like this:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=blighty.com;
  s=blueberry; t=1793116010;
  bh=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=;
  h=From:Subject:Date:To:From:Subject:Date:To:Reply-To:Cc;
  b=i0ZHnwEPNsdOrsf5vWDaIuqKCi7TWPIjFLlh2FtGqQHQEboXFtDkNBSHrFmPizi57
  e9sdrZRlFs4qNE6p87vTEMrjgxn4ar/aGxblRX8kh27wEbmO10eGBMD+uQtPa0poMr
  N0gjTaynR6XkgapD32VgMXOL8H+6UWBORA3qYgZkDeMGPq1n5l+esZWyfV6ox4szpx
  4yDIt1X5+CNKLGAI+7XHwHyZgX6dldcYGGlMLuM8j6xt95HxfzDnaJzjwZmRSb8YMM
  P85a+0L601L4ez9QbWLk/Q2LwG2Ji++cYACYAzbWvZ7bc2tYqLXZJ5inFaE+nxxuZ7
  8LnArgYh/ys5w==

We can see a=rsa-sha256 which confirms that we’ve used SHA256 to hash everything.

We see c=relaxed/simple which tells us that we’ve used simple canonicalization for the body.

And bh=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=, which is the simple body hash for an empty body we calculated earlier.

It really works.

An empty body is interesting

An empty body is simple to calculate, and it’s the one case where the expected hash is in the DKIM RFC so you can be sure you’ve got the right answer.

And body canonicalization is something DKIM code sometimes get wrong, particularly with the different handling of trailing CRLFs between simple and relaxed canonicalization. Checking that the body hash of an empty email is correct is a good smoke test of newly written or configured DKIM signing code.

Related Posts

Do we care about SPF alignment?

SPF and DKIM are the two main ways we associate a domain name with a stream of email in an authenticated way. We can choose the DKIM signing domain fairly freely – we can choose any domain or subdomain we control and put it in the d= field of the DKIM signature. But our choice for the SPF domain is more constrained.

Read More

Folding email headers

Why are my DKIM signatures failing at Microsoft?

There are several reasons why your DKIM signatures might be failing at Microsoft, but perhaps not at other major providers. Many of them are triggered by sending mail that’s “badly structured” in some way. This is one of them.

Read More

Google, Alignment and DMARC

Google has been making a number of changes to their systems over the last few weeks. Folks are seeing a lot of changes in Google postmaster tools and they’re seeing changes in how Google is displaying headers in the “show original” tab.

Read More