Where you see it
In the Authentication-Results header of a delivered message, or in a DMARC aggregate report. Gmail writes it as:
Authentication-Results: mx.google.com;
dkim=neutral (body hash did not verify) [email protected] header.s=s1 header.b=AbCdEf12;
Other receivers say dkim=fail with a similar reason. Gmail's neutral is still a failure as far as DMARC is concerned: the signature does not count.
What it actually means
A DKIM signature has two parts. bh= is a hash of the message body, taken when the message was signed. b= is the signature itself, over a set of headers and the DKIM-Signature header (which contains bh=). A receiver checks them in that order: it hashes the body it received, compares it with bh=, and only then fetches your public key and checks b=.
"Body hash did not verify" means the first step failed. The receiver never got as far as your key. So:
- It is not a DNS problem, a wrong selector or a bad key. Those give
no key for signature,key not foundorsignature did not verify. - It is not something you can see from outside. No DNS lookup, including this site's email deliverability checker, can tell you a body was altered in transit. What the checker can do is rule out the key side: it finds the selector's public key, checks it parses, and reports its size and whether it is in testing mode. If that is clean, you know to look at the path the message took.
What changes a body after signing
In rough order of how often I have found each one:
- A mailing list. Mailman, Google Groups, Sympa and the rest add a footer, prefix the subject, or re-encode the message. The signature was fine when it left you and broken when the list re-sent it. This is expected and the reason ARC exists: lists that add ARC headers let receivers such as Gmail see that the message passed when the list received it.
- A gateway or relay that adds a disclaimer. A legal footer, an "external sender" banner or an antivirus stamp added by a mail gateway after the message was signed. It is common when an application signs its own mail and then relays it through a corporate smarthost that appends a footer. Sign at the last hop instead, or move the footer before signing.
- Transfer encoding changed on the way. An 8-bit message relayed through a server that does not advertise
8BITMIMEgets converted to quoted-printable, which rewrites the body byte for byte. Sending 7-bit safe content (quoted-printable or Base64 from the start) avoids it. - Line endings or trailing whitespace. An application that signs a body with bare
LFline endings, which the MTA then converts toCRLF; or a relay that strips trailing spaces.c=relaxed/relaxedtolerates whitespace changes,c=simpletolerates almost nothing. If your signer usessimplefor the body, switch torelaxed. - Forwarding that rewrites. Most plain forwarding leaves the body alone, but some forwarders and security services (link rewriting, attachment stripping, "safe" previews) change it. What happens after delivery to a mailbox does not matter; what matters is anything that re-sends.
- A bug in the sender. The application builds the message, signs it, and then modifies it: adds a tracking pixel, re-wraps lines, appends an unsubscribe block. Signing has to be the last thing that happens.
How to find the one you have
- Send the same message straight to a Gmail or Outlook mailbox, not via a list or forwarder. If DKIM passes there and fails via the other path, the other path is the cause, and it is usually the list or the gateway.
- Read the
Receivedheaders from the bottom up. The signature was made at the hop belonging to thed=domain. Every hop above that is a suspect. A hop belonging to a list server, a filtering service or a relay you do not control is the likely one. - Compare the bodies. Save the message as sent (your application's log, or a copy sent to a mailbox you control) and as received, and diff them. The text diff checker will show an added footer or a changed encoding immediately. Remember that encoding changes show up as whole-body differences.
- Check the
c=andl=tags in theDKIM-Signatureheader.c=simple/simpleis fragile.l=(body length) is worse in the other direction: it lets anything be appended after the signed part, so do not use it to paper over footers.
Why it matters, and when it does not
DMARC passes if either SPF or DKIM passes and aligns with the From domain. A broken DKIM signature alone is survivable when SPF still passes and aligns. The trouble is that the paths that break DKIM (lists, forwarders) are exactly the ones that also break SPF, since the list's server is not in your SPF record. With both gone, a p=quarantine or p=reject policy sends the message to spam or refuses it. SPF vs DKIM vs DMARC explains the alignment rule; the DMARC policy guide covers when to tighten.
For mail you send yourself, fix the path. For mail to mailing lists you do not run, the list operator's fix is ARC or rewriting the From address; there is nothing you can do to your signature that survives a footer.