dkim

DKIM Body Hash (bh=) Mismatch: Why It Fails and How to Fix It

A DKIM body hash mismatch means the message body changed after signing, so the bh= value no longer matches what the receiver computes. This guide explains the difference between the body hash and the header signature, walks through the usual culprits (footer appenders, link rewriters, MIME re-encoding, canonicalization), and gives you a raw-body compare method plus the golden rule: sign at the last content-changing hop.

Jul 3, 20268 min read

A DKIM body hash mismatch means the message body was altered somewhere between the server that signed it and the server that verified it. DKIM stores a hash of the body in the bh= tag of the signature. When the receiver recomputes that hash from the body it actually received and gets a different value, verification fails with a body hash mismatch even though your private key and record are perfect. Nine times out of ten the fix is not the key or the DNS record. It is a hop that rewrote the body after signing.

Reads public DNS only. Nothing is stored unless you save the domain to an account.

What the bh= tag actually protects

A DKIM signature has two independent integrity checks, and confusing them is why body hash failures feel so mysterious.

The first is the body hash. The signing server runs the message body through a hash function (SHA-256 in modern setups) and writes the result into the bh= tag. A verifier hashes the body it received the same way and compares. If the two differ, you get a body hash mismatch and the whole signature is invalid, full stop.

The second is the header signature, held in the b= tag. This is an RSA or Ed25519 signature over the selected headers plus the value of bh=. It proves the signed headers and the body hash were not tampered with and that they came from the holder of the private key.

Here is the part that trips people up. If a receiver reports something like dkim=fail (body hash did not verify), your cryptography is fine. The key matched, the headers were intact, but the body arrived different from the body that was signed. That is a completely different problem from a signature did not verify error, which points at the key or the signed headers. Read the exact reason string in the Authentication-Results header before you touch anything, because it tells you which of the two checks broke.

A typical signature looks like this:

v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=selector1; h=from:to:subject:date; bh=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=; b=dzX9k...

The bh= is the body fingerprint. The c=relaxed/relaxed controls canonicalization, which we will get to because it decides how forgiving the body comparison is.

The usual culprits behind a bh= fail

Almost every body hash mismatch comes from one of a small set of intermediaries that edit the body after your server signs it.

Disclaimer and footer appenders

This is the number one cause. A compliance appliance, a mail gateway, or a mailing list adds a legal disclaimer, an unsubscribe footer, or a "scanned by" banner to the bottom of the message. That appended text changes the body, so the hash the sender computed no longer matches. The signing server had no idea the footer was coming, so bh= was calculated on the shorter, original body.

Mailing list managers are especially aggressive here. They add footers, rewrite subjects, and sometimes strip attachments, which is a big reason forwarded and list mail breaks authentication. The same class of problem hits SPF too, which is covered in why email forwarding breaks SPF.

Link rewriters and click tracking

Security products and marketing platforms rewrite every URL in the body so clicks route through their tracking or scanning domain. Rewriting even one link changes bytes in the body. If that rewrite happens after signing, the body hash is dead. This is common with URL defense features on inbound gateways and with tracking-enabled sends where the ESP signs before the tracker rewrites, or the reverse.

MIME re-encoding

Some relays normalize the message: they re-wrap long lines, switch a part from quoted-printable to base64, add or remove a trailing MIME boundary, or re-encode from 8bit to 7bit. None of this changes what a human reads, but it changes the raw bytes, and DKIM hashes raw bytes. A message that passes at one hop can fail one relay later purely from transport re-encoding.

Trailing whitespace and empty lines

DKIM has strict rules about blank lines at the end of the body. If a server adds or removes trailing empty lines, the hash can shift. Relaxed canonicalization handles some of this, but not all of it, which is the perfect segue to canonicalization.

Simple vs relaxed canonicalization

Canonicalization is the normalization DKIM applies before hashing, and the c= tag names two algorithms separated by a slash: header canonicalization then body canonicalization, for example relaxed/relaxed or simple/simple.

For the body, the two modes behave very differently:

  • simple tolerates almost nothing. The only thing it forgives is empty lines at the very end of the body. Any other whitespace change, including trailing spaces on a line or converted tabs, breaks the hash.
  • relaxed is far more forgiving. It reduces runs of whitespace inside a line to a single space, strips trailing whitespace at the end of each line, and ignores empty trailing lines. Minor reformatting by a well-behaved relay can survive it.

If you are seeing intermittent body hash failures and your signature uses simple body canonicalization, switching to relaxed/relaxed often stops the bleeding, because it absorbs harmless whitespace normalization that simple treats as tampering. Relaxed is the sane default for almost every sender. It does not, however, save you from a footer appender or a link rewriter. Those change real content, and no canonicalization mode will forgive that.

The golden rule: sign at the last content-changing hop

The durable fix is architectural, not a setting you toggle. DKIM must be applied by the last system that touches the body. If anything downstream edits the message, that edit has to happen before signing, or the signature has to be reapplied after the edit.

In practice:

  • If a gateway adds disclaimers, sign on that gateway or on a hop after it, not before.
  • If your ESP rewrites links for tracking, let the ESP sign the final rewritten message rather than signing upstream and handing it a signed body it will then mutate.
  • If a security appliance sits between your mail server and the internet and modifies mail, it must either be signing-aware or be positioned before the signer.

Order of operations is everything. A signature is a snapshot of the body at one instant. Every byte-level change after that instant invalidates it. The winning setups sign once, as late as possible, and change nothing afterward.

The before/after raw-body compare method

To catch the exact hop that breaks you, compare the raw body at two points.

  1. Capture the raw message the signing server emits. On many MTAs you can save a copy to a debug mailbox or read it from the outbound queue. Get the raw source, not the rendered view, because your mail client hides the bytes that matter.
  2. Capture the raw message as received at the destination. In Gmail use "Show original," in Outlook use "View message source," or pull it from the receiving server's store.
  3. Diff the two bodies byte for byte. Look specifically for an appended footer, rewritten URLs, a changed Content-Transfer-Encoding, re-wrapped long lines, or added trailing blank lines.

Whatever text differs between the two captures is your culprit, and the hop that introduced it is the hop that must sign or must move before the signer. This turns a vague "DKIM fails sometimes" into a specific, fixable change.

If, after this, the body is genuinely identical but DKIM still fails, the problem is no longer the body hash. It has moved to the header signature or to alignment, and you should look at DKIM alignment and confirm your record and selector against a clean DKIM setup.

Frequently asked questions

Does a body hash mismatch mean my DKIM key is wrong?

No. A body hash mismatch means the body changed after signing. Your private key, public key, and selector can all be correct and you will still see this failure. The error reason string usually says "body hash did not verify," which specifically rules out a key problem and points at message modification in transit.

Can I fix a bh= fail by changing canonicalization to relaxed?

Sometimes. Relaxed body canonicalization forgives whitespace normalization and trailing empty lines, so if a relay is only reformatting spacing, moving from simple to relaxed/relaxed can fix it. It will not help if a footer, disclaimer, or link rewriter is adding real content to the body. For those you have to sign after the modification.

Why does my email pass DKIM on some receivers but fail on others?

Different receiving paths apply different modifications. A message that reaches one provider directly may pass, while the same message routed through a forwarder, a mailing list, or a security gateway gets a footer or a rewritten link and fails. Forwarding is the classic offender, and it breaks SPF for the same structural reason.

How do I know if it is the body hash or the header signature that failed?

Read the Authentication-Results header on a received copy. "Body hash did not verify" is a body problem, so run the before/after raw-body compare. "Signature did not verify" points at the key or the signed headers instead. The two failures need different fixes, so identify which one you have before making changes.

Check your own domain

Run a free scan and get your grade with the exact records to fix.

Scan a domain

Related guides

DKIM Body Hash (bh=) Mismatch: Causes and Fixes