Skip to content
Authentication

The DKIM-Signature header: every tag decoded

The DKIM-Signature header carries the signature and everything needed to verify it: d, s, h, bh, b and more. What each tag means and which choices matter.

Everything a receiver needs to verify a DKIM signature travels in one header: DKIM-Signature. It names the signing domain and key, lists exactly which headers were signed, carries a hash of the body, and ends with the signature itself. Reading this header fluently turns DKIM from a black box into something you can debug from any delivered message.

A production signature, annotated below
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=example.com; s=s2026a; t=1752504000;
        h=from:from:to:subject:subject:date:mime-version:content-type;
        bh=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=;
        b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ...

The identity tags: d, s, i

d= names the signing domain and s= the selector; together they tell the receiver where to fetch the public key. d= is also the domain that DMARC alignment compares against the From: header, which makes it the single most consequential value in the header: a signature with your ESP's platform domain in d= verifies perfectly and does nothing for your DMARC. The optional i= tag identifies the signing agent (a user or subdomain under d=) and rarely needs attention.

The algorithm tags: a and c

a= declares the signing algorithm. rsa-sha256 is the production standard; rsa-sha1 is obsolete and refused by modern verifiers, and ed25519-sha256 exists in the spec with limited receiver support. c= sets the canonicalization modes for header and body, in that order: relaxed/relaxed tolerates the whitespace and wrapping changes that mail servers routinely make, while simple breaks on nearly any change. relaxed/relaxed is the correct default for surviving real-world transit.

The content tags: h, bh, b

h= lists the headers covered by the signature, in signing order. From must be included by specification; To, Subject, Date, and the MIME structure headers belong there in practice, since an unsigned header can be altered or added without breaking the signature. bh= is the hash of the canonicalized body, computed before signing. b= is the signature itself: the private-key operation over the canonicalized headers, including a reconstruction of the DKIM-Signature header with b= empty.

The lifetime tags: t and x

t= timestamps the signature; x= sets an expiry after which verifiers treat it as invalid. Expiry bounds how long a captured message can be replayed, at the cost of failing verification on slow delivery paths and late forwards, a trade-off weighed in the security hardening entry. Most senders leave x= unset and bound replay through key rotation instead.

The tag to never use: l

l= limits the body hash to the first N bytes, allowing anything appended beyond that point to ride along without invalidating the signature. It existed for a mailing list edge case and became an attack surface: 2024 research demonstrated practical content injection against senders using it, and current guidance from providers and the security community is unambiguous. Do not set l=, and treat its presence in any signature you audit as a finding.

Tag reference

FeatureRequiredMeaning
v=1DKIM version
a=rsa-sha256Signing algorithm
d= / s=Signing domain and selector: where the key lives, what DMARC aligns
h=Signed headers, From mandatory; repeat entries to oversign
bh= / b=Body hash and the signature value
c=no (simple/simple default)Canonicalization; use relaxed/relaxed
t= / x=Timestamp and optional expiry
l=never usePartial body coverage; a demonstrated attack surface

Frequently asked questions

Which headers should h= cover?
At minimum From (required), plus To, Cc, Subject, Date, Reply-To, Message-ID, and the MIME headers that define content structure. Oversign the mutable ones by listing them once more than they appear. Avoid signing hop-added headers like Received, which change in transit by design.
Why does b= wrap across multiple lines?
Header folding: long header values wrap for transport, and DKIM's canonicalization accounts for it. The signature value is read as one continuous base64 string regardless of the folding.
What does dkim=fail with body hash mismatch mean?
The received body no longer matches bh=, meaning content changed after signing: a footer-adding gateway, a rewriting mailing list, or an encoding change en route. The canonicalization entry covers the usual culprits.
Can I inspect a signature without any tools?
Yes, every delivered message shows the full DKIM-Signature header in its source view. Read d= and s= for identity, h= for coverage, and compare with the receiver's verdict in Authentication-Results one header block away.

Key takeaways

  • The DKIM-Signature header is self-describing: d= and s= locate the key, h= lists what is signed, bh= and b= carry the proof
  • d= is the domain DMARC alignment tests, which makes it the most consequential tag in the header
  • Use rsa-sha256 with relaxed/relaxed canonicalization as the production baseline
  • Oversign mutable headers by listing them once more than they appear in h=
  • Never set l=; partial body coverage is a demonstrated content-injection vector
DKIM-Signature header tags explained: d, s, h, bh, b | Inbox Theory