- September 2020
- Engineering Memo · External Release
A DKIM signature that verifies correctly at the time of signing may fail verification at the receiving end — not because of a key misconfiguration, but because the message content was modified between signing and verification. Encoding issues are the most common and least obvious cause of this modification: line endings get converted, base64 encoding is re-chunked, or MIME boundaries shift during transit through intermediate mail servers or content security systems. The result is a DKIM body hash mismatch that produces dkim=fail even when the signing infrastructure is correctly configured.
This note documents the specific encoding failure modes observed in production, how to diagnose which failure mode is occurring, and the configuration changes that prevent each type.
How DKIM Body Hashing Works and Where It Breaks
DKIM signing creates a hash of the message body (and selected headers) using the configured canonicalization algorithm, then signs that hash with the private key. The receiving server independently computes the same hash of the received message body and verifies it against the signature. If the message body as received differs from the message body as signed — even by a single character — the hash will not match and DKIM fails.
The canonicalization algorithm (specified in the c= tag of the DKIM-Signature header) determines how the message body is normalised before hashing. Two canonicalization modes exist: simple and relaxed. Simple canonicalization makes very few normalisation allowances — the body is hashed essentially as-is, with only trailing empty lines stripped. Any modification to the body after signing under simple canonicalization will break the signature. Relaxed canonicalization converts all whitespace sequences to a single space, converts line endings to CRLF, and strips trailing whitespace from each line before hashing — allowing most common in-transit whitespace modifications to pass verification without breaking the signature.
The vast majority of DKIM signature failures attributed to "encoding issues" are actually the result of using simple/simple canonicalization in environments where intermediate processing systems modify message content. The fix in most cases is as simple as changing the canonicalization to relaxed/relaxed. The more complex failure modes — base64 re-encoding, MIME boundary modification — require additional investigation.
Figure 1 — Where Encoding Modifications Break DKIM Body Hash
Failure Mode 1: Line-Length and Whitespace Modifications
The most common encoding failure mode is line-length modification. SMTP protocol limits lines to 998 characters, but messages sometimes contain lines longer than this — particularly in base64-encoded attachments or in HTML email bodies where a long URL or inline CSS property creates an excessively long line. When a message with long lines passes through a relay that enforces the 998-character limit, the relay inserts line-folding characters — inserting a CRLF and whitespace at the fold point. This modification changes the message body after signing, breaking simple/simple DKIM signatures.
The fix: use relaxed/relaxed canonicalization, which normalises whitespace before hashing and is tolerant of line-length folding. Additionally, configure the MTA to enforce line-length limits during message injection — adding line folding at the sending MTA before signing — ensures the body is in its final form before the hash is computed, rather than relying on downstream relays not to fold it. In PowerMTA, the max-smtp-line-length directive controls line-length enforcement on injected messages.
Whitespace at the end of lines is a closely related failure. Some MTA software strips trailing whitespace from message lines during processing — a common behaviour in text handling. Simple canonicalization is sensitive to this change: a body signed with trailing spaces on some lines fails verification on a system that strips trailing spaces. Relaxed canonicalization strips trailing whitespace before hashing, so both the signing and verification compute the same hash regardless of trailing whitespace presence.
Failure Mode 2: Content-Transfer-Encoding Changes
MIME parts in email can be encoded with different Content-Transfer-Encoding schemes: quoted-printable, base64, 7bit, 8bit, or binary. Some intermediate systems change the encoding of MIME parts — converting quoted-printable to base64, or converting base64 to quoted-printable — as part of their content normalisation or processing. This re-encoding changes the literal bytes of the message body, which changes the body hash, which breaks the DKIM signature under any canonicalization setting.
The diagnosis: compare the Content-Transfer-Encoding header value in the original message (from the sending MTA's accounting log or a test send's raw message source) against the value in the received message at the failing ISP. If they differ, an intermediate system is re-encoding the content. Identifying which intermediate system is making this change requires tracing the Received: header chain to find which hop introduced the re-encoding.
The fix depends on where the re-encoding occurs. If a content security scanner is re-encoding messages after inspection, configuring the scanner to pass MIME encoding unchanged (if the option exists) or bypassing the scanner for DKIM-signed traffic (if the security posture allows) resolves the issue. If the source is a mailing list processor that re-encodes as part of adding list headers, the DKIM signing must occur after the list processing — not before — so the signed body matches what ultimately gets delivered. This architectural sequencing is the critical point: DKIM signing must be the last modification to the message body before delivery, not an early step in a processing pipeline that further modifies the message.
Table 1 — DKIM encoding failure modes: diagnosis and fix
| Failure mode | Symptom | Diagnosis | Fix |
|---|---|---|---|
| Line-length folding | dkim=fail on messages with long HTML lines or URLs | Compare original vs received body for inserted CRLF | Use relaxed/relaxed canonicalization; enforce line-length at MTA |
| Trailing whitespace strip | dkim=fail only from specific relay paths | Compare raw message bytes for trailing space differences | Use relaxed/relaxed canonicalization |
| CTE re-encoding | dkim=fail on messages with attachments or multipart MIME | Compare Content-Transfer-Encoding header in sent vs received | Sign after last content-modifying processing step; configure scanner to pass encoding unchanged |
| MIME boundary modification | dkim=fail on multipart messages only | Compare boundary= parameter in Content-Type header | Exclude multipart-sensitive headers from h= list; sign after boundary is finalised |
| Mailing list footer addition | dkim=fail on messages processed through mailing list software | DKIM-Signature header references signing domain; list software adds body content after signing | Configure list software to remove upstream DKIM signature; sign with list domain after modification |
Failure Mode 3: MIME Boundary Changes
Multipart MIME messages use a boundary string to separate message parts — a unique string specified in the Content-Type header (boundary="some_unique_string") that appears as a delimiter between each MIME part in the message body. If an intermediate system modifies this boundary string — by changing its character casing, adding or removing whitespace, or replacing it entirely — the message body changes in a way that breaks the DKIM body hash regardless of canonicalization.
This failure mode is less common than line-length or CTE issues, because modifying boundary strings is not a standard behaviour of legitimate intermediate systems. When it occurs, it is typically the result of an email security appliance that reconstructs the message after scanning — creating new MIME boundaries as part of the reconstruction process rather than preserving the original boundaries. The diagnosis: compare the boundary= parameter value in the Content-Type header of the original versus received message. If they differ, a system is reconstructing MIME structure.
Mitigation when MIME boundary modification cannot be prevented: use DKIM body-length limits (the l= tag in the DKIM-Signature header) to limit the body hash to a specific number of bytes. This allows content to be appended after signing without breaking the signature, as the hash only covers the specified byte count. The limitation: body-length limits are also exploitable for replay attacks (appending malicious content after the signed portion), and some ISPs treat messages with l= body length limits with additional scrutiny. Use body-length limits only when no other option is available.
Diagnosing Encoding DKIM Failures: The Systematic Approach
When DKIM failures are reported or observed in DMARC aggregate reports, the diagnostic sequence is: (1) send a test message through the affected path and retrieve the raw message source from the receiving mailbox; (2) compare the raw received message against the message as it left the sending MTA (available from the accounting log or a separately captured copy at injection time); (3) identify any differences in the body, MIME headers, or Content-Transfer-Encoding; (4) trace the Received: header chain to identify which hop introduced the modification; (5) address the modification at that hop.
The comparison in step 2 requires byte-level accuracy — viewing the message in an email client is insufficient because email clients may normalise display without revealing the underlying byte differences. Use command-line tools (openssl, base64, hexdump) or dedicated MIME analysis tools to compare the raw bytes of sent versus received message bodies.
Gmail provides DKIM failure diagnostic information in the Authentication-Results header of received messages: the reason field describes why the DKIM signature failed. The most common reason strings: "body hash did not verify" indicates a body content modification after signing; "signature did not verify" indicates a header modification or key mismatch; "no key for signature" indicates the DKIM selector's DNS record cannot be found. Each reason string points to a different diagnostic path — body hash failures go to the encoding investigation described above; signature failures go to key configuration investigation; no-key failures go to DNS resolution investigation.
PowerMTA DKIM Configuration for Encoding Robustness
The PowerMTA DKIM configuration that minimises encoding-related failure risk:
<dkim-signature>
key-file /etc/pmta/dkim/yourdomain.com.pem
domain yourdomain.com
selector mail2025
algorithm rsa-sha256
canonicalization relaxed/relaxed # Tolerates whitespace modifications
header-list From:To:Subject:Date:Message-ID:MIME-Version:Content-Type
# Do NOT include Content-Transfer-Encoding in header-list
# if intermediate systems re-encode MIME parts
</dkim-signature>
The header-list configuration deserves specific attention. Including Content-Transfer-Encoding in the signed header list provides stronger authentication but makes the signature vulnerable to CTE re-encoding by intermediate systems. The trade-off: excluding CTE from the signed headers allows re-encoding without breaking the signature, at the cost of narrower header protection. For environments where CTE re-encoding by security scanners is confirmed or suspected, excluding Content-Transfer-Encoding from the header-list is the pragmatic choice.
Similarly, including the Content-Type header in the signed header list protects against MIME boundary changes — but breaks the signature if any system modifies the boundary parameter. For messages processed through systems that reconstruct MIME structure, excluding Content-Type from the signed headers eliminates this failure mode. The header-list configuration is a trade-off between authentication completeness and transit robustness; environments should configure it based on which intermediate systems their messages actually pass through, not based on theoretical maximum authentication coverage.
Monitoring DKIM Pass Rates for Encoding Drift
DKIM encoding failures do not typically appear suddenly — they appear when a new intermediate system enters the message path (a new security scanner, a new forwarding configuration, a new relay hop) that modifies content differently from previous systems. Monitoring DKIM pass rates via DMARC aggregate reports on a weekly cadence provides early detection of encoding drift: a pass rate that was 99.8% and drops to 97.5% indicates that a new failure mode has appeared, even if it hasn't yet reached a volume sufficient to affect domain reputation.
When an encoding failure is detected and fixed, verify the fix produces the expected improvement in DMARC aggregate reports within 48–72 hours. If the pass rate does not recover after a confirmed fix, a second failure mode may be present — encoding failures are not always single-cause, particularly in complex message processing environments with multiple intermediate systems. Work through each identified intermediate system systematically until the pass rate returns to the expected baseline.
The Forwarding Problem: Why DKIM Fails on Forwarded Messages
Email forwarding is the most widespread cause of DKIM failures that are entirely expected and unavoidable. When a recipient's email client or server forwards a message to another address, the message body may be modified by the forwarding system — adding a forwarding header, wrapping the original content in a new MIME structure, or simply re-encoding content during the forwarding process. Any of these modifications break the DKIM signature because the body hash no longer matches.
This is a structural limitation of DKIM when applied to forwarded messages. It is not a configuration problem that can be fixed at the sending infrastructure level. The implication for DMARC: when a DMARC policy evaluates messages at the ultimate destination (the address to which the message was forwarded), DKIM will often fail because of forwarding modification. SPF will also fail at the forwarded destination because the forwarding server's IP is not in the original sender's SPF record.
The mechanism designed to address this: ARC (Authenticated Received Chain), documented in RFC 8617. ARC allows forwarding servers to sign the authentication state of the message at the time of forwarding, creating a chain of authentication records that the final receiving server can evaluate. Gmail and some other major receiving servers support ARC evaluation when a message fails both DKIM and SPF due to forwarding — using the ARC chain to assess whether the original message was authenticated before forwarding. ARC is not universally implemented and does not guarantee delivery for forwarded messages with failed authentication, but it reduces false-positive DMARC failures from legitimate forwarding scenarios.
For operators: track the volume of forwarded message DKIM failures in DMARC aggregate reports by identifying source IPs that belong to forwarding services (Google's forwarding infrastructure, Apple's iCloud forwarding, enterprise email forwarding services). These failures are expected and do not indicate a configuration problem. Distinguishing expected forwarding failures from unexpected encoding failures requires classifying DMARC failure sources by IP type — forwarding service vs. direct sender — in the aggregate report analysis.
Character Set and Unicode Encoding in DKIM
Messages containing non-ASCII characters in headers — particularly in the From: display name, Subject:, or other human-readable header fields — use encoding schemes (RFC 2047 encoded-words or UTF-8 with SMTPUTF8) that can produce encoding variations depending on how intermediate systems handle the character representation. A header encoded as =?UTF-8?B?...?= (base64-encoded UTF-8) by the originating MTA may be received as =?UTF-8?Q?...?= (quoted-printable-encoded UTF-8) after passing through a system that re-encodes header values without changing their content.
If these headers are included in the DKIM signed header list (h=), the re-encoding changes the literal bytes of the header, which changes the header hash, which breaks the DKIM signature. The diagnosis: compare the raw header values in the sent versus received message for any headers containing non-ASCII encoded-words. If the encoding scheme differs (B= vs Q=, or the encoded content is the same but the encoding format changed), a system is re-encoding header values.
The fix: when sending messages with non-ASCII headers, ensure the MTA uses the encoding scheme that intermediate systems in the message path are least likely to change. UTF-8 with SMTPUTF8 (unencoded UTF-8 in headers) is the most modern approach but requires SMTPUTF8 support at every SMTP hop. RFC 2047 encoded-words are the most compatible approach, and base64 encoding (the =?charset?B?...?= form) is generally more stable than quoted-printable in transit because base64-encoded content has fewer opportunities for intermediate modification. The specific encoding choice depends on the intermediate systems in the deployment's message path.
Oversigning Headers to Prevent Injection After Signing
DKIM header oversigning is a signing technique that prevents a class of attacks where an attacker injects additional headers after the original signed headers, to make a signed message appear to have content it does not actually contain. Standard DKIM signing covers each signed header once — an attacker can add an additional unsigned header of the same type, and some verifiers may process the unsigned header as the authoritative one.
Oversigning adds a second signature entry for critical headers (particularly From:) that requires a second occurrence of the header to be present for the signature to verify. This prevents the injection attack: if a second From: header is added by an attacker, the signature fails because it was signed with only one From: header present. Legitimate messages have only one From: header, so oversigning From: does not affect valid message verification.
In PowerMTA, oversigning is configured by adding an asterisk prefix to header names in the header-list configuration: header-list *From:To:Subject:Date:Message-ID:MIME-Version. The asterisk before From: indicates oversigning for that header. Oversigning is recommended for the From: header as a standard security practice; oversigning additional headers is a configuration choice based on the specific attack surface the deployment needs to protect against.
Oversigning interaction with encoding failures: oversigning does not protect against body encoding modifications (those are body hash failures, not header injection). Oversigning specifically prevents header injection attacks. The two concerns are separate — oversigning is a security configuration for the header protection dimension; relaxed/relaxed canonicalization and line-length enforcement are the operational configuration for the body encoding dimension. Both are necessary in a complete DKIM configuration, and neither substitutes for the other.
Testing DKIM Encoding Robustness Before Production
Before deploying a new DKIM configuration or a new sending path that introduces intermediate processing systems, test DKIM robustness across the complete message path. Send test messages through each intermediate system and verify dkim=pass in the Authentication-Results header at the final destination. Test multiple message types: plain text messages, HTML messages with long lines, messages with attachments, and messages with non-ASCII header content. Each message type exercises different encoding paths that may interact differently with intermediate processing systems.
The test procedure for each message type: inject from the MTA → through all intermediate systems → to a test inbox at Gmail → retrieve raw message source → verify Authentication-Results shows dkim=pass and the body hash verified. If any message type fails, trace the specific body modification as described in the diagnostic section above and apply the corresponding fix before proceeding with production deployment.
Documenting the test results — which message types were tested, which intermediate systems they passed through, and the resulting dkim= outcome — creates a reference for future configuration changes. When a new intermediate system is added to the message path, the existing test documentation identifies which message types need re-testing and which were previously verified as robust. This systematic approach to DKIM robustness testing prevents the encoding failures that are discovered in production when the time pressure of a delivery incident makes diagnosis significantly harder than it would have been during planned pre-deployment testing.
DKIM encoding robustness is ultimately about managing the reality that email messages rarely travel from sender to recipient in a single hop. Every intermediate system is an opportunity for modification that was not anticipated when the DKIM signature was created. Configurations that assume a direct path — using simple/simple canonicalization, including all MIME headers in the signed header list, not enforcing line-length at injection — work in controlled environments and break in production as real-world message routing introduces intermediate hops. Configurations designed for transit robustness — relaxed/relaxed canonicalization, selective header signing, line-length enforcement at injection — work across the complex, variable-path reality of production email routing and produce consistently high DKIM pass rates that contribute to the domain reputation outcomes that good deliverability requires.
Infrastructure Assessment
Our managed infrastructure uses relaxed/relaxed DKIM canonicalization, enforces line-length limits at the MTA layer, and monitors DKIM pass rates weekly via DMARC aggregate report analysis. Encoding failures are caught before they affect inbox placement. Request assessment →