DKIM Canonicalization Explained: The Four Combinations (simple/simple, simple/relaxed, relaxed/simple, relaxed/relaxed) of RFC 6376 Section 3.4
The byte-level DKIM canonicalization reference: the two header algorithms (simple per RFC 6376 section 3.4.1 and relaxed per RFC 6376 section 3.4.2), the two body algorithms (simple per RFC 6376 section 3.4.3 and relaxed per RFC 6376 section 3.4.4), the four c= tag combinations, the optional l= partial-body-length tag with its security trade-off, the seven most common body-hash mismatch causes (forwarder whitespace folding, mailing-list footer injection, content-transform middleware, transfer-encoding changes, MIME re-encoding, trailing CRLF normalisation, Subject-prefix rewriting), the receiver-side forensic detection workflow via the Authentication-Results header, four worked byte-level examples covering each algorithm, and the per-provider canonicalization defaults for Microsoft 365, Google Workspace, Amazon SES, SendGrid, Mailgun, Mailchimp, Postmark, SparkPost, OpenDKIM, Postfix dkim-milter, and Exim DKIM. The canonical answer to DKIM body hash mismatch debugging.
This reference is provided in English. DKIM is specified in an English-language RFC (RFC 6376) and the canonical canonicalization vocabulary (simple, relaxed, c=, l=, bh=, b=, CRLF, WSP, FWS, body hash, signature) does not translate. We keep the body in the source language so technical terms remain consistent with the specifications.
TL;DR
- DKIM canonicalization is the normalisation step a DKIM signer applies to the message headers and the message body before hashing, so the signature survives the typical in-transit modifications (whitespace folding, continuation-line re-breaking, trailing-whitespace normalisation) that intermediate MTAs perform on the message. Defined in RFC 6376 section 3.4.
- Two algorithms × two surfaces = four
combinations. RFC 6376 §3.4 defines two
canonicalization algorithms (
simpleandrelaxed) and applies them independently to message headers and message body, producing four canonicalization combinations declared by thec=tag of theDKIM-Signatureheader in the formc=<header-canon>/<body-canon>. - Production guidance: use
c=relaxed/relaxedfor every modern DKIM deployment. Relaxed canonicalization survives whitespace folding, continuation-line re-breaking, and trailing-whitespace normalisation; simple canonicalization does not. Microsoft 365, Google Workspace, Amazon SES, SendGrid, Mailgun, Mailchimp, Postmark, SparkPost, and every modern SaaS sender default toc=relaxed/relaxed. - The RFC default is
c=simple/simple— when thec=tag is absent, RFC 6376 §3.5 specifiessimple/simpleas the default. This is a historical anomaly preserved for backward compatibility with the DomainKeys predecessor (RFC 4870) and is almost never the right production choice. Every modern signer explicitly emitsc=relaxed/relaxed. - The four canonicalization combinations:
(1)
c=simple/simple— exact bytes, fragile, the RFC default rarely used in production; (2)c=simple/relaxed— exact headers + transit-tolerant body, niche use case; (3)c=relaxed/simple— transit-tolerant headers + exact body, occasionally used for body-integrity-critical mail; (4)c=relaxed/relaxed— transit-tolerant on both surfaces, the production standard. - The seven most common mismatch causes are: (1) forwarder whitespace folding, (2) mailing-list footer injection, (3) content- transform middleware (antivirus, DLP, link-rewriting, URL-tracking), (4) transfer-encoding changes, (5) MIME re-encoding, (6) trailing CRLF normalisation, (7) Subject-prefix rewriting by mailing lists. Each is documented in section 8 with detection signatures and operational mitigations.
- The
bh=tag is the canonicalized-body hash; theb=tag is the signature over the canonicalized headers plus thebh=value. A body hash mismatch (commonly reported asdkim=fail (body hash mismatch)in the Authentication-Results header per RFC 8601) means the body was modified in transit; a signature mismatch (dkim=fail (signature verification failed)) means a signed header was modified.
1. Overview: why DKIM defines canonicalization
A DKIM signature is a cryptographic hash of the canonicalized message (selected headers + body) signed with the signing domains private key. The verifier recomputes the hash over the canonicalized received message and compares it to the signature. For this scheme to work, the signer and the verifier MUST agree on the exact byte sequence they are hashing.
The problem: mail in transit gets touched. The canonical list of in-transit modifications email architects accept as inevitable:
- Line-length folding. RFC 5322 section 2.1.1 recommends header lines no longer than 78 characters. Long lines get folded at column 78 (FWS — folding whitespace inserted) and refolded at different break points by intermediate MTAs. The visible header content is unchanged but the byte sequence is not.
- Whitespace normalisation. Some MTAs collapse runs of internal whitespace in headers to single spaces; some preserve them verbatim. Some strip trailing whitespace before the CRLF; some keep it. The semantic content of the header is unchanged but the byte sequence is not.
- Trailing empty lines in the body. Some MTAs append a trailing empty line to the body; some strip them; some normalise runs of trailing empty lines to a single CRLF. The body content is unchanged but the byte sequence is not.
- Continuation-line re-breaking. A multi-line header (e.g. a long Subject: that wraps) may be re-broken by an intermediate MTA at a different word boundary. The header content is unchanged but the byte sequence is not.
- Tab-to-space conversion. Some MTAs convert tabs to spaces in headers (one tab becomes one or more spaces). The visible content is similar but the byte sequence is not.
- Date-header insertion. An MTA may add a Date: header if the sender omitted one. Adding a header does not affect signed headers already in the message, but a signer that named Date: in the h= list expecting the senders Date: will see an MTA-inserted Date: at verification time.
- Message-ID rewriting. Some MTAs rewrite the Message-ID: header when the sender omitted one or when the sender used a non-canonical format.
Without canonicalization, any of these benign modifications would invalidate every DKIM signature on every message that passed through a transforming MTA. With canonicalization, the signer and the verifier both apply a deterministic normalisation that maps the modified byte sequence back to the same canonical form, so the hash matches and the signature verifies.
RFC 6376 section 3.4 defines two algorithms with deliberately different trade-offs:
simple— minimal normalisation, fragile, exact-byte. Survives ONLY the most trivial mail-in-transit modifications (a single trailing empty line in the body). Any byte change beyond that invalidates the signature. Useful when bit-exact identity must be preserved.relaxed— robust normalisation. Header field names lower-cased, whitespace runs collapsed, continuation lines unfolded, trailing whitespace stripped. Survives the typical in-transit modifications. The production default for every modern signer.
The two algorithms apply independently to headers
and body, so a DKIM signer can choose any of the
four combinations. The c= tag of the
DKIM-Signature header (RFC 6376 section 3.5)
declares the choice in the form
c=<header-canon>/<body-canon>.
2. The four canonicalization combinations
Two algorithms (simple, relaxed) applied to two surfaces (headers, body) produces four combinations. Each has a documented use case, but in practice production deployments concentrate on one combination almost exclusively.
| Combination | Header canonicalization | Body canonicalization | Use case |
|---|---|---|---|
c=simple/simple |
Exact bytes (the strictest header form) | Exact bytes with trailing CRLF collapse only | The RFC default if c= is
omitted. Niche use cases requiring
exact-byte signature integrity (notarised
mail, legal-discovery archives). Almost
never used in production. |
c=simple/relaxed |
Exact bytes | Whitespace collapsed, trailing whitespace stripped, trailing empty lines removed | Body-tolerant deployments that still require exact-byte headers — uncommon. Some legacy enterprise gateways used this combination historically. |
c=relaxed/simple |
Header names lower-cased, continuation lines unfolded, whitespace runs collapsed, trailing whitespace stripped | Exact bytes (with trailing CRLF collapse) | Header-tolerant deployments that still require body integrity. Niche use cases (signed contracts, signed transactional receipts where any body byte change must invalidate the signature). |
c=relaxed/relaxed |
Header names lower-cased, continuation lines unfolded, whitespace runs collapsed, trailing whitespace stripped | Whitespace collapsed, trailing whitespace stripped, trailing empty lines removed | The production standard. Every modern DKIM signer defaults to this combination. Survives the typical in-transit modifications. Recommended for every notification, transactional, marketing, and corporate mail deployment. |
The 95-percent rule: in real-world
DKIM signing-corpus surveys, c=relaxed/relaxed
accounts for approximately 95% of all DKIM
signatures. The remaining ~5% is mostly legacy
senders that have not migrated from the RFC default,
plus a handful of intentional niche deployments.
Operators auditing their own outbound DKIM should
aim for 100% c=relaxed/relaxed across
all signers.
3. Header canonicalization algorithms
RFC 6376 section 3.4 defines two header
canonicalization algorithms. Each transforms the
signed headers (those named in the h=
tag of the DKIM-Signature header) into a
deterministic byte sequence before hashing.
3.1 The simple header algorithm (RFC 6376 §3.4.1)
Definition: The simple
header canonicalization performs essentially no
transformation on signed headers. The header lines
are presented to the hash function exactly as they
appear in the message, byte-for-byte. The only
rule is that no normalisation is applied at all.
Algorithm: Per RFC 6376 §3.4.1,
the simple header canonicalization
algorithm "does not change header fields in any
way." Each signed header field (named in the
h= tag) is passed to the hash function
in the order listed, with its original CRLF
terminator, exactly as it appeared in the message
when the signer received it. The DKIM-Signature
header itself is included last, with the
b= tag value temporarily replaced by
an empty string for the hash computation (the
b= value is what the hash produces).
Use case: Niche. The
simple header canonicalization is
intended for mail flows where exact-byte header
integrity must be preserved — notarised mail,
legal-discovery archives, some end-to-end signed
communication systems that do not traverse
transforming intermediaries. The RFC default is
simple only for backward compatibility
with the DomainKeys predecessor; modern deployments
should not rely on the default.
Pitfalls: (1) Any intermediate MTA
that folds long header lines at a different column
than the signer used will invalidate the signature
— and most production MTAs do exactly that.
(2) Tab-to-space conversion in headers (common in
Microsoft Exchange transit) invalidates the
signature. (3) Continuation-line re-breaking
invalidates the signature. (4) Header case
transformations (a verifier that lower-cases the
Subject: to subject: before signing math) interact
badly with simple because no
case-normalisation is applied. The cumulative
effect is that simple header
canonicalization fails to verify on a substantial
fraction of real-world mail flows even when the
semantic message is unchanged.
3.2 The relaxed header algorithm (RFC 6376 §3.4.2)
Definition: The relaxed
header canonicalization applies four normalisations
to each signed header: header field name
lower-casing, continuation-line unfolding,
internal-whitespace-run collapsing, and
leading/trailing whitespace stripping. The combined
effect produces a canonical byte sequence that
survives the typical in-transit modifications.
Algorithm: Per RFC 6376 §3.4.2,
the relaxed header canonicalization
algorithm transforms each signed header as follows:
- Convert all header field names to lower-case. "Subject: Hello" becomes "subject: Hello". The colon and the value are not affected.
- Unfold all header continuation lines. A header that spans multiple physical lines (folded with CRLF + WSP per RFC 5322 section 2.2.3) is rejoined into a single logical line. The CRLF + WSP sequence at the fold point is replaced with the unfolded form.
- Convert all runs of WSP (whitespace, SP or HTAB) to a single space. Multiple consecutive spaces or tabs anywhere in the header value become a single SP character.
- Delete any WSP before and after the colon that separates the header field name from its value. The colon itself is retained.
- Strip all trailing WSP from the header value (between the last non-whitespace character and the CRLF terminator).
- Terminate every signed header with a single CRLF; only the DKIM-Signature header itself, which is hashed last, goes to the hash function without a trailing CRLF (RFC 6376 §3.7).
Use case: Production. The
relaxed header canonicalization is the
default choice for every modern DKIM deployment.
It survives line-length folding, continuation-line
re-breaking, whitespace normalisation, and
tab-to-space conversion — the four most common
in-transit header modifications. Microsoft 365,
Google Workspace, Amazon SES, SendGrid, Mailgun,
Mailchimp, Postmark, SparkPost, Klaviyo, Constant
Contact, HubSpot, OpenDKIM, Postfix dkim-milter,
and every modern SaaS sender emit
c=relaxed/... on outbound mail.
Pitfalls: (1) The header field name lower-casing rule means the verifier must lower-case received header names before lookup — a verifier that compares "Subject:" to "subject:" case-sensitively will fail to find the signed header. (2) The whitespace-run collapsing means a verifier MUST collapse multiple internal spaces; verifiers that preserve them will compute a different hash and reject the signature. (3) The unfolding rule means a verifier MUST unfold continuation lines BEFORE collapsing whitespace; doing the operations in the wrong order produces a different hash. (4) The l= tag interaction (see section 6) applies only to the body, not the headers — there is no header-length truncation.
4. Body canonicalization algorithms
RFC 6376 section 3.4 defines two body
canonicalization algorithms. Each transforms the
message body into a deterministic byte sequence
before hashing. The result is hashed with the
algorithm declared in the a= tag
(typically rsa-sha256 or
ed25519-sha256 per RFC 8463) and the
resulting hash is base64-encoded and placed in the
bh= tag of the DKIM-Signature header.
4.1 The simple body algorithm (RFC 6376 §3.4.3)
Definition: The simple
body canonicalization performs almost no
transformation. The body bytes are presented to
the hash function exactly as they appear, with the
single rule that consecutive trailing empty lines
at the very end of the body are collapsed to a
single CRLF.
Algorithm: Per RFC 6376 §3.4.3,
the simple body canonicalization
algorithm applies one transformation:
- Collapse consecutive trailing empty
lines at the very end of the body to a single
CRLF. If the body ends with
...content\r\n\r\n\r\n\r\n, the canonicalized form is...content\r\n(the final CRLF terminator is retained; the extra trailing empty CRLFs are stripped). If the body is empty, the canonicalized form is a single CRLF.
All other body bytes — including internal empty lines, internal whitespace, MIME boundaries, and every byte of every line — are preserved exactly as received.
Use case: Niche. The
simple body canonicalization is
intended for mail flows where exact-byte body
integrity must be preserved — signed contracts,
signed transactional receipts where any body byte
change must invalidate the signature, end-to-end
notarised mail. The RFC default is simple
only for backward compatibility.
Pitfalls: (1) Any intermediate
MTA that adds a footer to the body (mailing-list
subscribe/unsubscribe links, anti-virus banners,
DLP modifications) invalidates the signature.
(2) Any MTA that re-wraps lines (a transfer-encoding
change from quoted-printable to 7bit or vice versa,
a MIME re-encoding that adjusts line lengths)
invalidates the signature. (3) Trailing-whitespace
differences (some MTAs strip trailing spaces from
lines; some preserve them) invalidate the
signature. (4) The l= tag (see section 6)
can mitigate footer-injection but introduces a
security weakness where post-cutoff content is
unsigned. The cumulative effect is that
simple body canonicalization fails to
verify on a substantial fraction of real-world
mail flows.
4.2 The relaxed body algorithm (RFC 6376 §3.4.4)
Definition: The relaxed
body canonicalization applies three normalisations
to the body: internal-whitespace-run collapsing,
trailing-whitespace stripping, and trailing-empty-line
stripping. The combined effect produces a canonical
byte sequence that survives the typical in-transit
modifications without introducing the l= tag
security weakness.
Algorithm: Per RFC 6376 §3.4.4,
the relaxed body canonicalization
algorithm applies three transformations:
- Within each body line, reduce all runs of WSP (whitespace, SP or HTAB) to a single space. Multiple consecutive spaces or tabs anywhere in the line become a single SP character.
- Strip all trailing WSP from each body line (between the last non-whitespace character and the CRLF terminator).
- Strip all trailing empty lines at the end of the body (including any trailing CRLF that would follow a final empty line). If the body is empty after this transformation, the canonicalized form is the empty string.
Use case: Production. The
relaxed body canonicalization is the
default for every modern DKIM deployment. It
survives whitespace folding within lines,
trailing-whitespace differences, and trailing-empty-
line variations — the three most common in-transit
body modifications. Combined with relaxed header
canonicalization, c=relaxed/relaxed is
the production standard.
Pitfalls: (1) Even relaxed canonicalization does NOT survive footer injection (mailing-list subscribe/unsubscribe links added to the body) — any byte added to the canonical body changes the hash. The mitigation is From-header munging on the list side, ARC sealing per RFC 8617, or the l= partial-body tag (with its security trade-off). (2) The whitespace-run collapsing applies WITHIN lines but does NOT collapse newlines — a verifier that confuses internal whitespace with line boundaries will compute a different hash. (3) The trailing-empty-line stripping applies at the end of the body but does NOT remove internal empty lines — a verifier that strips ALL empty lines will compute a different hash. (4) MIME boundary lines are body content per the RFC and are subject to the same canonicalization — verifiers that special-case MIME boundaries will compute a different hash.
5. The c= tag: declaring canonicalization in the signature
The c= tag in the DKIM-Signature header
(RFC 6376 section 3.5) declares the two
canonicalization algorithms the signer used. The
verifier MUST apply the same algorithms to
reproduce the signers byte sequence and verify the
hash.
Syntax:
c=<header-canon>/<body-canon>
where each value is either simple or
relaxed. The four possible values:
c=simple/simple— exact-byte headers + exact-byte body (with trailing CRLF collapse).c=simple/relaxed— exact-byte headers + relaxed body.c=relaxed/simple— relaxed headers + exact-byte body.c=relaxed/relaxed— relaxed headers + relaxed body. Production standard.
Default: If the c=
tag is absent from the DKIM-Signature header, the
default value is simple/simple per
RFC 6376 section 3.5. This default is widely
considered a historical anomaly preserved for
backward compatibility with the DomainKeys
predecessor (RFC 4870); modern signers should
ALWAYS emit an explicit c= tag.
Example DKIM-Signature header with explicit c= tag (the production-standard form):
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=k202604;
h=from:to:subject:date:message-id;
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
b=AuyqDpoCYHKNdvOOO7yvgblHA9zMlqNXBOFv5WGdmZuTLrRyYbWO9JX
O+ZA7vNRoYZpiTKuBVhFGRMcCxw6lQ4WAEBvfh5KuKjLrhgGPyiK1pdj
oqUlnxqGQQfqLR3PpO5VC5W4nQzWfRtLwiR3PpO5VC5W4nQzWfRtLwiR=
The c=relaxed/relaxed tag in this
example tells the verifier to apply relaxed
canonicalization to both the signed headers (the
h= list: from, to, subject, date,
message-id) and the message body before recomputing
the hash. The bh= tag is the
base64-encoded body hash; the b= tag
is the base64-encoded signature over the
canonicalized headers plus the bh=
value.
Operator guidance: Audit your
outbound DKIM signatures with an Authentication-
Results-aware tool (the DMARC Analyzer
Email Header Analyzer
parses every signed-header tag including
c=). Every modern signer should emit
c=relaxed/relaxed; if you see
c=simple/simple on your own outbound
mail, your signer is probably misconfigured or
using a legacy default.
6. The l= tag: partial body signing and its risks
The l= tag in the DKIM-Signature header
(RFC 6376 section 3.5) is an OPTIONAL signed-body-
length declaration. When present, l=N
tells the verifier to canonicalize and hash ONLY
the first N octets of the canonicalized body,
ignoring everything after that.
Intended use case: Allow mailing-
list forwarders to append a footer
(subscribe/unsubscribe links, list-info banner)
without breaking the signature. The signer sets
l= to the original canonicalized body
length, the list appends a footer, the verifier
hashes only the first l octets (which
are unchanged), and the signature verifies.
The security weakness: The l= tag creates an asymmetry between what the signature attests and what the recipient sees. An attacker who intercepts a signed message can append arbitrary content after the l= cutoff. The appended content is UNSIGNED but to a casual reader appears to be part of the signed message — and the signature still validates. The canonical attack: an attacker intercepts a signed transactional message confirming a $100 payment, appends "...PS your account has been compromised, please transfer $10000 to acct 1234-5678 immediately", and forwards the message. The signature still validates because the original $100 confirmation bytes are unchanged in the first l octets. The recipient sees a DKIM-verified message that appears to authentically instruct a fraudulent transfer.
Why modern signers omit l=: The security weakness above outweighs the operational benefit. Mailing lists that need to append footers almost always also rewrite the Subject: or From: headers (which break header canonicalization regardless of l=), so the l= tag does not actually rescue the signature. The canonical mitigations for mailing-list breakage are From-header munging (rewrite the From: to the lists own domain and re-sign with the lists DKIM), ARC sealing per RFC 8617 (preserve the original authentication results across modifications), or accepting the DKIM failure and relying on SPF + alignment.
Operator guidance: Do NOT enable the l= tag in your signer unless you have an explicit threat-modelled use case where the asymmetry is acceptable. The default in every modern signer (OpenDKIM, Microsoft 365, Google Workspace, Amazon SES, SendGrid, Mailgun, Mailchimp, Postmark, SparkPost) is to omit l= entirely — sign the entire canonicalized body, accept the forwarder breakage when it happens, and rely on the operational mitigations.
7. The seven most common body-hash mismatch causes
Across thousands of DKIM verification failures analysed in DMARC aggregate-report data, the same seven mismatch causes recur. Each cause states the in-path modifier, its detection signature in the Received headers, and the operational mitigation.
7.1 Forwarder whitespace folding
Cause: An intermediate forwarder (an iCloud Mail Drop, a Gmail filter forwarder, a .forward file, an /etc/aliases expansion, a sieve rule) folds long lines at a different column than the signer used. The forwarders SMTP daemon may refold continuation lines at SMTP line-length limits (1000 octets per RFC 5321 section 4.5.3.1.6).
Detection: A Received header from a known forwarding service immediately precedes the dkim=fail Authentication-Results entry.
Mitigation: Use
c=relaxed/relaxed (the whitespace
folding is normalised away by relaxed). If you
must use c=simple, deploy ARC sealing
at the forwarder.
7.2 Mailing-list footer injection
Cause: A mailing-list forwarder (Mailman, Discourse, Google Groups, Sympa, GitLab list notifications, GitHub list notifications) appends a footer to the message body with subscribe / unsubscribe links and list-info banner. The appended bytes change the canonicalized body and invalidate the body hash.
Detection: A Received header from a list-server domain (lists.example.com, discuss.example.com, etc.) precedes the dkim=fail Authentication-Results entry. The message body contains a recognisable list footer.
Mitigation: Configure the list to
use From-header munging (rewrite From: to the
lists own domain and re-sign with the lists DKIM)
via Mailmans
dmarc_moderation_action setting,
Google Groups footer settings, or Discourses
moderation rules. Do NOT use the l= tag as the
mitigation — see section 6.
7.3 Content-transform middleware (antivirus, DLP, URL-rewriting)
Cause: A content-transform middleware (Microsoft Defender Safe Links rewrites URLs to safelinks.protection.outlook.com proxy URLs; Mimecast / Proofpoint URL Defense rewrites URLs to their proxy domains; antivirus middleware injects scan-result banners; DLP middleware redacts sensitive strings) modifies the message body in transit.
Detection: Received headers from safelinks.protection.outlook.com, urldefense.proofpoint.com, mimecast.com, or a corporate email gateway domain immediately precede the dkim=fail entry. The message body contains rewritten URLs or scan banners.
Mitigation: If you control the middleware, configure URL-rewriting to occur AFTER DKIM verification (so the verifier sees the original body). If you do not control the middleware, accept the DKIM failure and rely on SPF alignment or ARC sealing. Many corporate gateways now emit ARC seals to preserve the pre-rewrite authentication result.
7.4 Transfer-encoding changes
Cause: An intermediate MTA converts the message body Content-Transfer-Encoding from quoted-printable to 7bit, from 7bit to quoted-printable, or from quoted-printable to base64 (or vice versa). The transfer-encoding change re-wraps lines, re-encodes non-ASCII characters, and changes the byte sequence even though the semantic content is unchanged.
Detection: The Content-Transfer-Encoding header value in the received message differs from the value the signer embedded (if Content-Transfer-Encoding was named in the h= list). Received headers show passage through a known transcoding gateway (some Exchange Server versions transcode aggressively).
Mitigation: Include
Content-Transfer-Encoding in the
h= signed-headers list so the
encoding change is detected as a header
canonicalization failure (which is easier to
diagnose than a silent body-hash mismatch). If
transcoding cannot be avoided, deploy ARC sealing
at the transcoding gateway.
7.5 MIME re-encoding (boundary rewrites, part reordering)
Cause: An intermediate MTA re-packages a multipart MIME message with different boundary strings, reorders the parts, normalises the Content-Type parameters, or strips attachments (iCloud Mail Drop replaces attachments over 25 MB with a download link). The MIME structure changes even though the semantic content is preserved.
Detection: The MIME boundary strings or part order in the received message differ from typical signer output. Received headers show passage through Apple iCloud Mail or a similar attachment-handling intermediary.
Mitigation: Avoid attachments over the receiver-side size limits where possible. For MIME-rewriting intermediaries you do not control, accept the DKIM failure and rely on SPF alignment or ARC sealing.
7.6 Trailing CRLF normalisation
Cause: An intermediate MTA adds or removes a trailing empty line at the end of the body, or normalises multiple trailing empty lines to a single CRLF (or strips them entirely). The semantic content is unchanged but the byte sequence at the end of the body differs.
Detection: The verifier reports a body hash mismatch and a hex-dump comparison of the signers canonical body vs the verifiers received body shows only differences at the very end of the body.
Mitigation: Use relaxed body
canonicalization, that is
c=relaxed/relaxed (RFC
6376 §3.4.4 strips trailing empty lines as part of
the normalisation, so this class of difference is
canonicalized away). c=simple body
canonicalization (RFC 6376 §3.4.3) only collapses
a run of trailing empty CRLFs to a single CRLF —
less tolerant than relaxed.
7.7 Subject-prefix rewriting by mailing lists
Cause: A mailing list rewrites
the Subject: header to add a list-name prefix
("[list-name]" or "[LIST] " prepended to the
original subject). The Subject: header is almost
always in the h= signed-headers list,
so any rewrite invalidates the header
canonicalization.
Detection: The received Subject: header has a "[...]" prefix that was not in the senders original Subject:. Received headers show passage through a list server.
Mitigation: Configure the list to
disable Subject-prefix rewriting (Mailman:
subject_prefix = ""; Discourse:
disable the email reply suffix), OR configure the
list to use From-header munging + re-sign with the
lists own DKIM (which makes the list the
authentic signer, accepting the lists own
Subject-prefix as part of the authenticated
signal).
8. Receiver-side detection workflow
When a DKIM verification fails, the verifier records
the failure in the Authentication-Results header
(RFC 8601).
The header contains a dkim= field with
the verification verdict and a parenthetical reason
string. The five canonical reason strings:
dkim=pass— verification succeeded.dkim=fail (body hash mismatch)— the body was modified in transit; thebh=tag does not match the verifiers computed body hash.dkim=fail (signature verification failed)— a signed header was modified in transit; the body hash matched but theb=signature does not verify against the canonicalized headers.dkim=neutral— verifier processed the signature but could not reach a definitive verdict.dkim=temperror— DNS lookup for the selector record failed transiently; the verifier could not retrieve the public key.
The forensic workflow:
- Capture the raw message source as the verifier received it. Outlook: File > Properties > Internet headers + Save As to preserve the body. Gmail: three-dot menu > Show original > Download Original. Apple Mail: View > Message > Raw Source. The body must be byte-exact — any UTF-8 conversion or line-ending normalisation by your tooling will distort the diagnosis.
- Read the DKIM-Signature header.
Identify the
v=,a=,c=,d=,s=,h=,bh=, andb=tag values. Thec=tag tells you which canonicalization algorithms to apply. - Read the Authentication-Results
header. The receivers verifier verdict
is the source of truth for the failure mode.
Look for
dkim=fail (...)and the parenthetical reason. - Apply the declared body canonicalization
to the received body. Compute the body
hash with the algorithm declared in
a=(typically rsa-sha256). Compare tobh=. If they differ, the body was modified in transit — proceed to step 5. If they match, the failure is in header canonicalization or the signature math — proceed to step 6. - Walk the Received headers backward to find the first hop where the body was modified. The immediately-preceding hop is the culprit. Common culprits: list servers, content-transform middleware, forwarding services, transcoding gateways.
- For header-canonicalization failures,
apply the declared header canonicalization to
each signed header (in the
h=order) and recompute the signature. The DMARC Analyzer Email Header Analyzer automates steps 4-6 and surfaces the specific failure mode.
9. Worked byte-level examples
9.1 Header simple canonicalization
Signers original Subject: header (with two-space indent on the continuation line):
Subject: Re: your order
is ready for pickup
Header simple canonical form (byte-for-byte identical, no transformation):
Subject: Re: your order
is ready for pickup
If an intermediate MTA refolds the continuation line at a different column, the simple canonical form changes and the signature breaks.
9.2 Header relaxed canonicalization
Same signers original Subject: header:
Subject: Re: your order
is ready for pickup
Header relaxed canonical form (lower-cased name, continuation unfolded, whitespace runs collapsed, whitespace around the colon and at the end of the value stripped; the value is followed by a single CRLF):
subject:Re: your order is ready for pickup
The relaxed form survives any subsequent refolding, case-change, or whitespace normalisation by an intermediate MTA because the same normalisation is applied at verification time.
9.3 Body simple canonicalization
Signers original body (note the run of three trailing empty lines):
Your order #12345 has shipped.
Thank you for your business.
Body simple canonical form (only the trailing empty-line run is collapsed to a single CRLF; all internal content is byte-for-byte identical):
Your order #12345 has shipped.
Thank you for your business.
Any other byte change to the body invalidates the simple body hash.
9.4 Body relaxed canonicalization
Signers original body (note trailing spaces on the first content line and the run of trailing empty lines):
Your order #12345 has shipped.
Thank you for your business.
Body relaxed canonical form (whitespace runs collapsed to single spaces within each line, trailing whitespace stripped from each line, trailing empty lines stripped from the end):
Your order #12345 has shipped.
Thank you for your business.
Note that the internal empty line is preserved — relaxed canonicalization only strips trailing empty lines at the END of the body, not internal empty lines.
10. Provider canonicalization defaults
Every modern mailbox provider and ESP defaults to
c=relaxed/relaxed for outbound DKIM
signing. The provider-by-provider breakdown:
| Provider | Default c= |
Configurable? | Notes |
|---|---|---|---|
| Microsoft 365 (Exchange Online) | relaxed/relaxed |
No (admin cannot change) | Microsoft signs all outbound mail with
c=relaxed/relaxed and
a=rsa-sha256. The signer
is part of the Exchange Online transport
pipeline. |
| Google Workspace (Gmail) | relaxed/relaxed |
No (admin cannot change) | Gmail signs all outbound mail with
c=relaxed/relaxed and
a=rsa-sha256. Workspace
admins manage selectors via the Admin
console. |
| Amazon SES | relaxed/relaxed |
No (SES manages signing) | SES signs all sent mail with
c=relaxed/relaxed on the
customers verified domain (or
amazonses.com if the domain is not
verified). |
| SendGrid | relaxed/relaxed |
No (SendGrid manages signing) | SendGrid signs with
c=relaxed/relaxed on the
authenticated domain configured via the
Sender Authentication wizard. |
| Mailgun | relaxed/relaxed |
No | Mailgun signs with
c=relaxed/relaxed on the
domain configured via Sending Domains. |
| Mailchimp / Postmark / SparkPost | relaxed/relaxed |
No | All three default to
c=relaxed/relaxed. The
sending-domain authentication wizard
provisions the DKIM selector and
manages the signing key. |
| OpenDKIM (self-hosted) | simple/simple (legacy) |
Yes (Canonicalization
relaxed/relaxed in opendkim.conf) |
OpenDKIMs documented default is
simple/simple — operators
MUST explicitly set
Canonicalization
relaxed/relaxed in opendkim.conf.
This is the #1 self-hosted DKIM
misconfiguration. |
| Postfix dkim-milter | Inherits from OpenDKIM | Yes | Same configuration surface as
OpenDKIM — set
Canonicalization
relaxed/relaxed in
opendkim.conf. |
| Exim DKIM | relaxed/relaxed |
Yes
(dkim_canon = relaxed) |
Exim defaults to relaxed for both surfaces; explicit configuration is still recommended. |
Audit your own outbound DKIM:
Send a test message to a mailbox you control,
capture the raw source, and read the
c= tag of the DKIM-Signature header.
Every modern signer should emit
c=relaxed/relaxed; the
DMARC Analyzer
Email Header Analyzer
parses every DKIM-Signature tag and flags
non-production canonicalization choices.
11. FAQ
- What is DKIM canonicalization?
- DKIM canonicalization is the normalisation step a DKIM signer applies to the message headers and the message body before they are hashed and signed, and the same normalisation a DKIM verifier applies before re-hashing the headers and body to compare against the signature. Defined in RFC 6376 section 3.4, canonicalization exists because mail in transit gets touched: SMTP intermediaries fold long lines at column 78, refold continuation lines at different break points, normalise trailing whitespace, collapse runs of spaces, convert tabs to spaces, add a trailing empty line, strip a trailing empty line, or re-encode the body during MIME repackaging. Without canonicalization, ANY of these benign transformations would invalidate the signature because the hash would not match. With canonicalization, the signer and the verifier agree on a deterministic textual representation of the message that survives the typical mail-in-transit modifications, so the signature stays valid. RFC 6376 section 3.4 defines two algorithms (simple and relaxed) and applies them independently to headers and body, producing four canonicalization combinations declared by the c= tag of the DKIM-Signature header.
- What is the difference between simple and relaxed canonicalization?
- Simple canonicalization (RFC 6376 sections 3.4.1 for headers and 3.4.3 for body) performs essentially NO normalisation. Header simple keeps headers byte-for-byte exactly as the signer presented them. Body simple keeps body bytes exact, with the single rule that consecutive trailing empty lines at the very end of the body are collapsed to a single CRLF. Relaxed canonicalization (RFC 6376 sections 3.4.2 for headers and 3.4.4 for body) performs substantial normalisation. Header relaxed lower-cases header field names, unfolds folded header continuation lines back into single logical lines, collapses runs of whitespace within the header value to single spaces, strips leading and trailing whitespace from the header value, and removes the trailing whitespace before the CRLF. Body relaxed collapses runs of whitespace within each line to single spaces, removes trailing whitespace at the end of each line, removes trailing empty lines, and (like simple body) collapses consecutive trailing empty lines. The relaxed-vs-simple trade-off is straightforward: relaxed survives more mail-in-transit transformations, simple guarantees exact-byte identity. Production DKIM almost always uses c=relaxed/relaxed.
- What does c=relaxed/relaxed mean in a DKIM signature?
- The c= tag in the DKIM-Signature header (RFC 6376 section 3.5) declares the two canonicalization algorithms the signer used, in the form c=header-canon/body-canon. c=relaxed/relaxed means the signer used relaxed canonicalization for headers AND relaxed canonicalization for body. The verifier MUST apply the same canonicalization the signer declared; using the wrong canonicalization will produce a different hash and reject the signature. c=relaxed/relaxed is the production default for every modern DKIM deployment because it survives the typical in-transit transformations (whitespace refolding by intermediate MTAs, header continuation-line re-breaking, trailing-line normalisation) that would invalidate a simple canonicalization. The four possible c= values are: c=simple/simple (the RFC default if c= is omitted, fragile and rarely used in production), c=simple/relaxed (mixed, occasionally used for security-critical headers + transit-tolerant body), c=relaxed/simple (mixed, the body bytes are signed exactly), and c=relaxed/relaxed (production standard).
- What does it mean when DKIM verification fails with body hash mismatch?
- A DKIM body hash mismatch (commonly reported as dkim=fail with the body hash reason, or as bh= mismatch in verifier logs) means the body hash the signer computed and embedded in the DKIM-Signature header (the bh= tag) does NOT match the body hash the verifier computed after applying the same canonicalization (declared by the c= tag) to the received message body. Body hash mismatches are almost always caused by an in-path modification of the message body between the signer and the verifier: a mailing list appended a footer, an antivirus middleware rewrote a URL, a transfer-encoding change re-wrapped lines, a DLP system replaced a sensitive string, an MTA re-encoded a MIME boundary, an iCloud Mail Drop attachment-handler stripped attachments, or a forwarder added or removed trailing empty lines. The fix is to identify the in-path modifier (use the Received headers to trace the message hops and the Authentication-Results header to find where verification first failed), then either configure the modifier to leave the body alone, or accept the change and rely on ARC (RFC 8617) or a downstream re-signing scheme to attest the modified body, or use c=relaxed/relaxed (more transit-tolerant) instead of simple body canonicalization.
- Why does my DKIM signature break after the message goes through a forwarder?
- Forwarders break DKIM signatures because the forwarder is an intermediate MTA that may modify the message in ways the original signers canonicalization did not anticipate. The four most common forwarder-induced DKIM failures are: (1) header rewriting (the forwarder rewrites the Subject:, To:, or From: header to add a forwarded-from prefix or list-name tag — every signed header that gets rewritten fails verification); (2) body footer injection (mailing-list forwarders append a footer with subscribe / unsubscribe links — every byte added to the signed body breaks the body hash unless the signer used an l= tag with a length shorter than the original body); (3) MIME re-encoding (the forwarder repackages a multipart message with different boundary strings or different Content-Transfer-Encoding choices — every byte change in the MIME structure breaks the body hash); (4) transfer-encoding changes (the forwarder converts a quoted-printable body to 7bit, or vice versa — the line-wrapping changes break the body hash). The canonical mitigations are ARC sealing (RFC 8617) at the forwarder, From-header munging on mailing lists (rewrite the From: to the lists own domain and re-sign with the lists DKIM), or accepting the DKIM failure and relying on SPF + DMARC alignment via DKIM only when the forwarder leaves the body intact.
- What is the l= tag in a DKIM signature?
- The l= tag in the DKIM-Signature header (RFC 6376 section 3.5) is an OPTIONAL signed-body-length declaration. When present, l= tells the verifier to canonicalize and hash ONLY the first l octets of the canonicalized body, ignoring everything after that. The intended use case is to allow mailing-list forwarders to append a footer to the message without breaking the signature: the signer sets l= to the original body length, the forwarder appends a footer, the verifier hashes only the first l octets (which are unchanged), the body hash matches, and the signature verifies. The l= tag is rarely used in practice for three reasons: (1) it introduces a security weakness where an attacker can inject arbitrary content after the l= cutoff and the signature still validates (the appended content is unsigned but to a casual reader appears to be part of the signed message); (2) most forwarders that append footers also rewrite the From: header (breaking header canonicalization regardless), so the l= tag does not actually help; (3) modern DKIM signers default to omitting l= (signing the entire body) and accept the forwarder breakage, preferring ARC sealing or From-header munging as the operational answer. Use l= only with explicit threat modelling — never as a casual operational workaround.
- Why does RFC 6376 default to c=simple/simple if no c= tag is present?
- RFC 6376 section 3.5 specifies that if the c= tag is absent from a DKIM-Signature header, the default value is c=simple/simple. This default is widely considered a historical anomaly rather than a sensible production choice. The default exists because the original DomainKeys specification (RFC 4870, DKIMs predecessor) did not have a relaxed canonicalization and simple was the only option; RFC 6376 preserved backward compatibility by keeping simple as the default. In practice, every modern DKIM signer (OpenDKIM, Microsoft 365, Google Workspace, Amazon SES, SendGrid, Mailchimp, Postfix dkim-milter, Exim DKIM, Sendmail DKIM, all the SaaS senders) explicitly emits c=relaxed/relaxed, so the c= default rarely fires. Operators inspecting a DKIM-Signature header should ALWAYS see an explicit c= tag in modern mail — the absence of the c= tag is itself a diagnostic signal (likely a legacy signer or a misconfigured custom implementation). When auditing your own signers, verify the c= tag is explicitly set to relaxed/relaxed and not relying on the default.
- How do I debug a DKIM body hash mismatch?
- The forensic workflow for diagnosing a DKIM body hash mismatch has five steps. Step 1: Capture the raw message source as the verifier received it (Outlook File > Properties > Internet headers; Gmail three-dot menu > Show original; Apple Mail View > Message > Raw Source). The body must be byte-exact — any UTF-8 conversion or line-ending normalisation by your tooling will distort the diagnosis. Step 2: Read the DKIM-Signature header and identify the c= tag (header-canon/body-canon), the bh= tag (the original body hash in base64), the b= tag (the signature), the d= tag (signing domain), and the s= tag (selector). Step 3: Apply the body canonicalization (simple or relaxed per c=) to the received body and compute the hash with the same algorithm declared in the a= tag (typically rsa-sha256). Step 4: Compare your computed body hash to the bh= value. If they differ, the body was modified in transit. If they match, the failure is in header canonicalization or the signature math, not the body. Step 5: Walk the Received headers backward to find the first hop where the body was modified — the immediately-preceding hop is the culprit. The DMARC Analyzer Email Header Analyzer automates steps 2-4 and surfaces the specific failure mode.
- Which DKIM canonicalization should I use for production?
- Use c=relaxed/relaxed for every production DKIM deployment. Relaxed canonicalization for both headers and body survives the typical in-transit transformations (whitespace folding, continuation-line re-breaking, trailing-whitespace normalisation) that would invalidate a simple canonicalization. Microsoft 365, Google Workspace, Amazon SES, SendGrid, Mailgun, Mailchimp, Postmark, SparkPost, Klaviyo, Constant Contact, HubSpot, and every modern SaaS sender default to c=relaxed/relaxed. The handful of legitimate c=simple/simple use cases are niche: notarised email where any byte change must invalidate the signature (legal-discovery archives, transactional notarisation services), some highly-regulated industries that require provably exact signatures, and certain end-to-end signed-mail systems that do not pass through transforming intermediaries. For ordinary notification, transactional, marketing, and corporate mail — c=relaxed/relaxed is the right answer. Configure your signer (OpenDKIM AlwaysSignHeaders, Postfix dkim-milter, Microsoft 365 default, etc.) to emit c=relaxed/relaxed explicitly.
13. Verify your DKIM canonicalization now
The fastest way to confirm your outbound DKIM is
emitting c=relaxed/relaxed and the
signature is verifying end-to-end:
- Send a test message to a mailbox you control (a personal Gmail account, a seed-list address, the DMARC Analyzer mailbox you ingest reports through).
- Capture the raw source — Gmail three-dot menu > Show original > Download Original; Outlook File > Properties > Internet headers; Apple Mail View > Message > Raw Source.
- Paste the headers into the
Email Header
Analyzer. The DKIM-Signature panel
shows every tag, including the
c=value. - Verify the Domain Security Checker confirms your DKIM selector publishes a valid public key.