← glossary

$ authentication

What is DKIM, and how does the signature work?

DKIM adds a cryptographic signature to outgoing mail so a receiver can verify the message was authorised by the signing domain and has not been altered.

DKIM — DomainKeys Identified Mail, defined in RFC 6376 — attaches a cryptographic signature to every outgoing message. A receiving server fetches the matching public key from DNS, verifies the signature, and learns two things: which domain took responsibility for the message, and whether the signed parts have changed since it was sent.

Unlike SPF, DKIM says nothing about which machine sent the mail. A correctly signed message can be relayed through any number of servers and still verify.

The two halves

In the message, a DKIM-Signature header:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
  d=yourapp.com; s=pharos1; t=1789251600;
  h=from:to:subject:date:message-id;
  bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
  b=hGfPQr1n4TZ...

In DNS, the public key at <selector>._domainkey.<domain>:

pharos1._domainkey.yourapp.com.  IN  TXT  "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."

The tags that matter:

  • d= — the signing domain. This is what DKIM authenticates, and what DMARC later compares against the From: header.
  • s= — the selector. It names which key to fetch, which is how a domain can hold several keys at once. That is what makes rotation possible without downtime.
  • h= — which headers were signed. Anything not listed can be changed in transit without breaking the signature.
  • bh= — a hash of the body. If a single byte of the body changes, this stops matching.
  • b= — the signature over the signed headers and the body hash.

What a pass actually proves

That the holder of the private key for d= signed this message, and that the signed headers and the body have not been altered since.

It does not prove the message is wanted, that the sender is honest, or that d= has anything to do with the address in the From: header. A spammer can sign their own mail with their own valid DKIM key, and it will pass. That is not a flaw — DKIM establishes who is accountable, and reputation systems do the rest.

The gap between d= and From: is the one that matters to you, and closing it is DMARC alignment.

Key length and rotation

Use 2048-bit RSA. 1024-bit keys still verify but are below what large receivers now expect, and some DNS providers make the longer record awkward to publish — which is the usual reason a 1024-bit key is still in place.

Rotate by publishing a second selector, switching signing to it, and removing the first only after nothing in the queue was signed with it. Mail already sent and not yet delivered still needs its key to resolve. Selectors exist precisely so this is a non-event.

Why DKIM survives forwarding and SPF does not

Forwarding changes the path but not usually the message. SPF checks the path, so it fails. DKIM checks the message, so it passes — provided nothing rewrote the body or a signed header on the way.

Mailing lists are the exception that proves it: a list that appends a footer or rewrites the subject invalidates the body hash or a signed header, and DKIM fails. That is the intended behaviour. The message genuinely was modified.

The l= tag

DKIM allows an optional l= tag limiting how much of the body is covered by the hash. It exists so a footer appended in transit does not break the signature.

Do not use it. Anything past the signed length can be replaced with arbitrary content while the signature still verifies, which turns a valid DKIM pass into a way to put words in your mouth. Leave the body hash covering the whole body.

Checking it

dig +short TXT pharos1._domainkey.yourapp.com

Then read the Authentication-Results header on a message that actually arrived:

Authentication-Results: mx.google.com;
  dkim=pass header.i=@yourapp.com

dkim=pass with header.i or d= on your own domain is the result worth having. dkim=pass on someone else’s domain is a pass that will not help you at the DMARC stage.

Pharos signs with a key issued per sending domain and verifies the DNS record before the domain may send. How a send leaves covers where that fits in the path.