CertaDNS
Skip to lesson

What a Signature Covers · lesson 1 of 3

The two hashes

After this lesson you can

Say precisely which bytes of a message a signature commits to, and which it does not.

SPF authorises a host. DKIM authenticates a message. The difference is the whole of why DKIM survives a forward and SPF does not, and it starts with knowing exactly which bytes the signature commits to.

Two hashes, not one

A signer computes two things, and both end up in the header it adds.

  • The body hash. A SHA-256 digest of the message body, canonicalised. It lands in the bh= tag.
  • The signature. An RSA or Ed25519 signature over a canonicalised copy of the headers named in h= — plus the DKIM-Signature header itself, with its own b= value blanked. It lands in b=.

The body is therefore covered indirectly: the signature covers bh=, and bh= covers the body. That indirection is the reason a receiver can report the two failures separately, and it is what makes body hash did not verify such a precise diagnostic.

What a verifier does

1. Read the DKIM-Signature header.
2. Fetch the key at  <s>._domainkey.<d>
3. Canonicalise the body, hash it, compare to bh=
4. Canonicalise the h= headers, verify b= against the key
5. Report one result

Step 3 before step 4 is not arbitrary — RFC 6376 §6.1.3 has the verifier check the body hash first precisely so that a body modification is reported as a body modification rather than as a generic signature failure.

What is not covered

  • Any header not named in h=. A header absent from the list can be added, changed or removed freely without touching the signature.
  • The envelope. DKIM never sees MAIL FROM or RCPT TO. A signed message can be re-sent to a completely different recipient and the signature still verifies — which is the mechanism behind replay.
  • The connecting IP. Deliberately. That is SPF’s evidence, and tying the signature to it would destroy the property that makes DKIM worth having.
  • Time. Unless the signer sets x=, the signature does not expire.

A signature is a statement about bytes, not about intent

A valid signature says: the domain in d= held the private key for selector s=, and these headers and this body are as that domain released them. It says nothing about who sent it, to whom, from where, or whether the domain meant to send it at all.

Try it on a real domain

Free, no account, public DNS only.

Last reviewed