The DKIM Body Hash (bh=), Explained Properly
What bh= actually contains, how it's computed separately from the header signature, and why understanding the split between the two matters for real troubleshooting.
Where bh= Fits Into the Bigger Signature Picture
A DKIM-Signature header is dense with tags, and it's easy to treat bh= as just one more parameter among many without understanding the specific structural role it plays. The overall signature (the b= tag) is computed over a defined set of signed headers — and critically, the DKIM-Signature header itself, with its b= value blanked out, is one of the headers included in what gets signed. Since bh= is a tag within that same DKIM-Signature header, it's effectively signed as part of the header signature too. This is the mechanism by which body integrity gets folded into the overall signature: rather than signing the (potentially huge) body directly, DKIM hashes the body separately into a small, fixed-size digest, embeds that digest as bh= inside the header, and then signs the whole header set including that embedded digest. Tamper with the body, and the hash you'd need to recompute to match the embedded bh= value won't match, causing the overall signature check to fail.
How bh= Is Actually Computed
| Step | What Happens |
|---|---|
| 1 | Take the raw message body as it will be transmitted |
| 2 | Apply body canonicalization (simple or relaxed, per the c= tag) to normalize formatting |
| 3 | If an l= tag is present, truncate to only the specified byte length |
| 4 | Compute a cryptographic hash (typically SHA-256) over the resulting canonicalized body |
| 5 | Base64-encode the resulting hash and embed it as the bh= tag's value |
A verifier performs the identical process independently on the received message's body, using the canonicalization mode and l= value (if any) declared in the signature it's checking, and compares its own computed hash against the bh= value embedded in the signature. A mismatch at this stage is specific and diagnostic: it means the body, as received, does not hash to the same value as the body that was originally signed, under the declared canonicalization rules.
Body Hash Mismatch vs. Header Signature Failure
The l= Tag: What It's For, and Why It's Risky
RFC 6376 defines an optional l= tag specifically to support scenarios where content legitimately needs to be appended to a message after it's been signed — a mailing list adding a footer, for instance. Setting l= to a specific byte count tells a verifier to compute the body hash over only that many leading bytes, ignoring anything beyond it. This does solve the specific append-after-signing problem it was designed for, but it does so by creating a deliberate blind spot: a verifier honoring l= is explicitly not checking the integrity of anything past the specified length, which means an attacker who can insert content into that unverified trailing portion can do so without breaking the signature at all. Because of this real security trade-off, using l= is widely discouraged in current guidance, and many verifiers apply extra scrutiny to signatures that use it, precisely because it's a specification feature more often associated with attempted abuse than legitimate use in current practice.
Practical Diagnosis: What a Mismatch Usually Means in the Field
In real-world troubleshooting, a body hash mismatch traces back to a small set of recurring causes: a relay or forwarding system modifying the body in a way the declared canonicalization mode doesn't tolerate (frequently simple canonicalization interacting with routine in-transit whitespace or line-ending changes), a mail client or gateway that rewrites links or inserts tracking pixels into HTML bodies after the message was already signed, or, less commonly, genuine tampering. Working through these in order — checking canonicalization mode first, since it's the most common and most benign explanation, before assuming malicious intent — tends to resolve most real cases quickly.
Walking Through a Full Verification, Body Hash Step Included
To see exactly where the body hash fits into a complete verification, it helps to walk the full sequence a verifier performs, not just the body-specific portion in isolation. First, the verifier extracts the DKIM-Signature header from the received message and parses its tags, including d=, s=, c=, h=, bh=, and b=. Second, it uses d= and s= together to construct and query the correct selector hostname in DNS, retrieving the public key. Third, it reconstructs the canonicalized form of every header listed in h= (using whichever header canonicalization mode c= specifies), plus the DKIM-Signature header itself with its b= value blanked out, exactly as the original signer would have. Fourth, and this is where bh= specifically enters the process, it independently canonicalizes the received body according to the body canonicalization mode in c=, hashes the result, and compares that computed hash against the bh= value embedded in the header set from step three. If they don't match, verification fails right there, before even reaching the final cryptographic signature check. If they do match, the verifier proceeds to the fifth step: using the retrieved public key to cryptographically verify that b= is a valid signature over the full reconstructed header set (which, remember, includes the now-confirmed-matching bh= value baked into it). Only if both the body hash comparison and the cryptographic signature check succeed does the overall DKIM verification pass.
Why Two Separate Checks Instead of One Combined Check
It's a reasonable question why DKIM bothers with a separate body-hash comparison step at all, rather than simply including the raw body directly in whatever gets cryptographically signed in one single operation. The practical answer is efficiency and flexibility. Cryptographic signature algorithms operate most efficiently over a small, fixed-size input; hashing the body down to a compact digest first, then only cryptographically signing the (also comparatively small) header set that includes that digest, avoids needing the signature algorithm itself to process a potentially large body directly. It also cleanly separates two conceptually different integrity questions — "has the body been altered" and "has the header set, including this body summary, been altered and is it authentically signed" — into two distinct, independently reasoned-about steps, which is part of why understanding them as separate failure modes, as covered above, is genuinely useful for troubleshooting rather than just a technical curiosity.
SHA-1 Versus SHA-256 for Body Hashing
RFC 6376 originally allowed SHA-1 as a hashing option alongside SHA-256, but SHA-1 has since become widely deprecated across the broader cryptographic landscape due to demonstrated practical collision vulnerabilities — meaning it's become possible, with sufficient computational effort, to construct two different inputs that hash to the same SHA-1 value, which directly undermines the integrity guarantee a hash is supposed to provide. Current guidance strongly favors SHA-256 exclusively for new DKIM deployments, and many verifying implementations treat a SHA-1-based signature with the same increased skepticism they'd apply to a 1024-bit RSA key, for closely related reasons: a hashing algorithm with known weaknesses undermines the same integrity guarantee that key length undermines when too short, just through a different mechanism.
How Body Hash Diagnostics Differ Across Common Client and Server Tools
Different tools people reach for when diagnosing a DKIM issue expose body hash information with varying degrees of directness. Command-line mail testing utilities and dedicated DKIM verification libraries typically report the computed versus expected body hash explicitly on mismatch, making the diagnosis immediate. Consumer email clients' "view original message" features generally show the raw DKIM-Signature header including the bh= value, but require manually recomputing the expected hash yourself (or using a separate tool) to actually compare against it, since the client itself usually isn't performing or reporting DKIM verification as part of that view. A dedicated DKIM lookup and verification tool bridges this gap by fetching the relevant public key, performing the canonicalization and hashing itself, and reporting a direct pass/fail specifically on the body hash component, which is considerably faster for diagnostic purposes than manually working through the raw header data.
Body Hash Behavior With Empty or Near-Empty Message Bodies
An edge case worth understanding: a message with a completely empty body (unusual, but technically valid) still produces a bh= value, since hashing an empty input is a well-defined operation for any standard hash algorithm, simply producing the hash algorithm's specific fixed digest for zero-length input. This isn't a special case DKIM needs to handle differently — the canonicalization and hashing process applies uniformly regardless of body length, including the degenerate case of no body content at all. Where this can matter practically is in troubleshooting: if a body hash mismatch occurs for what should be a body-free or near-empty message, the mismatch usually points to something being appended to a nominally empty body (an unexpected trailing newline, a signature block some system silently adds, or similar) rather than to any DKIM-specific quirk around handling empty content.
Body Hash Behavior Across Character Encodings
Message bodies can be encoded in various character sets, and canonicalization operates on the body's actual byte representation as transmitted, not on some encoding-independent abstraction of its text content. This means a message re-encoded from one character set to another in transit — even if the visible text renders identically to a human reader afterward — produces genuinely different underlying bytes, and therefore a different body hash, since canonicalization has no awareness of character encoding semantics beyond treating the body as the byte stream it actually is.
Body Hash Diagnostics as Evidence in Disputed Delivery Cases
In situations where a sender and recipient disagree about whether a message was delivered exactly as sent — a dispute over the content of a contractual notification, for instance — a body hash mismatch or match can serve as objective, verifiable evidence one way or the other, independent of either party's own recollection or claims. A matching body hash, verified against the originally signed content, is strong evidence the body reached the recipient unaltered from what was signed; a mismatch is equally strong evidence something changed somewhere along the delivery path. This isn't DKIM's primary design purpose, but it's a genuine, useful secondary benefit of having cryptographic integrity verification built into the delivery chain at all.
Related Reading in This Series
For the canonicalization rules that directly determine how the body hash is computed, see DKIM Canonicalization. For DKIM's overall signing and verification flow, read What Is DKIM. For how a stale or mismatched signature can be exploited, see DKIM Replay Attacks. To inspect a real DKIM signature and its body hash, open DKIM Lookup.
ToolsNovaHub tools are built and independently maintained with a focus on accurate, no-signup network and security utilities. Spotted an error? Let us know.
📋 Related Tools & Guides Comparison
| Resource | Type | Link |
|---|---|---|
| DKIM Lookup | Tool | Open Tool → |
| DKIM Canonicalization | Guide | Read Guide → |
| What Is DKIM | Guide | Read Guide → |
| DKIM Replay Attacks | Guide | Read Guide → |
| DMARC Lookup | Tool | Open Tool → |