DKIM-Signature Header Explained: Byte-by-Byte Tag Anatomy (RFC 6376 §3.5 — Every Tag from v= a= c= d= s= h= bh= b= through i= q= l= t= x= z=, the Two-Hash Design, the Signed-Header Set Reconstruction Algorithm, and Malformed-Header Detection Patterns)
The sender-side byte-by-byte reference for the DKIM-Signature header per RFC 6376 §3.5. Documents every one of the 14 canonical tags: the 7 required tags (v= a= d= s= h= bh= b=) and the 7 optional tags (c= i= q= l= t= x= z=) with format, defaults, validity rules, failure modes, and operational guidance. Covers the two-hash design (bh= body-hash independent of b= signed-header-set signature), the signed-header set reconstruction algorithm including the self-referential DKIM-Signature header handling with b= temporarily empty, the l= replay-attack vector that RFC 6376 §8.2 itself cautions against, the t=-yes-x=-no timestamp guidance, the rsa-sha256 modern default per RFC 8301, the ed25519-sha256 alternative per RFC 8463, and malformed-header detection patterns. Natural sender-side companion to help_dkim_failures_explained.php (the receiver-side verdict catalogue): together they form the canonical DKIM message-level reference where this page documents what the signer EMITS and the failures page documents what the verifier OUTPUTS.
This reference is provided in English. RFC 6376 tag syntax (v= a= c= d= s= h= b= bh= i= q= l= t= x= z=) and DKIM-Signature header format do not translate. We keep the body in the source language so technical terms remain consistent with IETF RFC documentation.
TL;DR
- 14 canonical tags per
RFC 6376 §3.5: 7 REQUIRED (
v=,a=,d=,s=,h=,bh=,b=) and 7 OPTIONAL (c=with a default,i=,q=,l=,t=,x=,z=). - Two hashes, not one:
bh=is the body hash;b=is the signature over the signed-header set INCLUDING the DKIM-Signature header itself (withb=temporarily emptied for the signing computation). Most dkim=fail comes frombh=mismatch (forwarder rewrote the body). - Modern defaults:
v=1,a=rsa-sha256,c=relaxed/relaxed,q=dns/txt(the only defined query method). rsa-sha1 is forbidden by RFC 8301; simple/simple and 1024-bit keys are permitted but fragile or discouraged, and stricter receivers may score them down. - NEVER use
l=. The body-length-limit tag is a documented replay-attack vector (attacker appends content past the signed prefix and the signature still verifies). RFC 6376 §8.2 itself cautions against it. t=yes,x=usually no. The signing timestamp gives receivers replay-detection material. Hard expiration viax=trades replay defense for clock-skew failure modes — set generously (24+ hours pastt=) or omit.h=must cover From. RFC 6376 §5.4 mandates signing the From: header; a verifier must ignore a signature whoseh=omits it (dkim=permerror). Modern signers cover From, Subject, Date, To, Message-ID, Reply-To, Sender, MIME-Version at minimum.d=drives DMARC alignment, noti=. The DMARC engine compares thed=tag to the From: header per RFC 7489 §3.1 — the M365onmicrosoft.comgotcha and Workspacegappssmtp.comgotcha are bothd=misalignment bugs (dkim=pass but DMARC fails).- Tag ordering and case: tags MAY appear in any order; tag NAMES are case-sensitive lowercase per RFC 6376 §3.2 ABNF; tag VALUES vary by tag (case-sensitive too unless the tag's description says otherwise, as with the domain in d=). Convention: tags appear in the order v;a;c;d;s;t;x;h;bh;b but verifiers do not depend on it.
What this guide covers
A complete byte-by-byte reference for the
DKIM-Signature header per RFC 6376 §3.5.
Where
DKIM
explained documents the high-level
signing pipeline (key generation, canonical
body-hash, signature production, verifier
workflow) and
DKIM
canonicalization documents the
c= tag's
simple-vs-relaxed semantics in depth, THIS
page documents EVERY tag in the
DKIM-Signature header. Each tag gets its own
section: what it carries, what its valid
values are, what default applies when
absent, what operational decisions it
encodes, and what failure modes it
produces.
The page is the SENDER-SIDE companion to DKIM failures explained, which documents the RECEIVER-SIDE verdict catalogue. The two pages together form the canonical DKIM message-level reference: this page documents what the signer EMITS; the failures page documents what the verifier OUTPUTS after evaluating it.
Operators arriving here typically have a DKIM-Signature header they need to decode, debug, or audit — either inspecting an inbound message to understand the sender's posture, or inspecting an outbound message to verify their own signer's output. The decision matrix section at the end maps from "I see this tag value" to "this is what it means operationally".
Shape of a DKIM-Signature header
A complete DKIM-Signature header is a
single long header value (typically
line-folded for readability) containing
semicolon-separated tag=value
pairs:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=selector1;
t=1715430123;
h=From:To:Subject:Date:Message-ID:Reply-To;
bh=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=;
b=AbCdEf1234567890PqRsTu+/0987WxYz1234567890ABCDef
ghIJklmnopQRSTuvWXYZabcdef1234567890abcdefghIJKL
mnopqrstUVWXYZ1234567890abcdef==
Whitespace between tags is irrelevant (per RFC 6376 §3.2 ABNF the inter-tag whitespace is folded out during parsing). The header MAY be expressed entirely on one logical line; folding into multiple visual lines is a presentation convenience.
The 14 tags
The RFC 6376 §3.5 tag dictionary, with required/optional status:
REQUIRED:
v= Version (always 1)
a= Algorithm (rsa-sha256, rsa-sha1 deprecated, ed25519-sha256)
d= Signing-domain identifier (drives DMARC alignment)
s= Selector (locates the public-key TXT in DNS)
h= Signed-header list (colon-separated header names)
bh= Body hash (base64-encoded SHA hash of canonical body)
b= Signature (base64-encoded crypto signature of signed-header set)
OPTIONAL (defaults apply when absent):
c= Canonicalization (default: simple/simple — but modern signers
always emit relaxed/relaxed)
i= AUID (default: @<d=value>)
q= Query method (default: dns/txt — the only defined method)
l= Body-length limit (default: none — DO NOT USE)
t= Signing timestamp (default: none — receivers MAY require it)
x= Expiration timestamp (default: none — never expires)
z= Copied headers (default: none — diagnostic-only)
A signer MUST emit all REQUIRED tags; omitting any one produces a malformed DKIM-Signature header that verifiers reject with dkim=permerror. The OPTIONAL tags are signer-chosen — their absence means the spec's default applies.
v= — Version
Required. The DKIM
protocol version. The only valid value is
v=1.
What it carries
A single integer identifying the protocol
version under which the signature was
produced. RFC 6376 §3.5 specifies
v=1; no other version has
ever been published. Even algorithm
extensions (RFC 8301 deprecating rsa-sha1,
RFC 8463 adding Ed25519) did not bump the
version — they extended the
a= tag's value space
instead.
Validity rules
- MUST appear (REQUIRED).
- MUST equal
1. - Conventionally the FIRST tag in the header; verifiers do not depend on this convention.
Failure mode
A DKIM-Signature without v=
or with v= not equal to 1
is REJECTED by verifiers per RFC 6376
§6.1.1. The verdict is dkim=permerror with
reason "incompatible DKIM version" or
similar.
a= — Signing algorithm
Required. The cryptographic algorithm pair: signature algorithm + hash algorithm.
Defined values
a=rsa-sha256— modern default. RSA signature + SHA-256 hash. Mandatory per RFC 8301 and the only algorithm most receivers accept without question.a=rsa-sha1— forbidden by RFC 8301 §3.1: it MUST NOT be used for signing or verifying. SHA-1 is broken; verifiers that follow RFC 8301 treat signatures using it as permanently failed. Senders MUST migrate away.a=ed25519-sha256— introduced by RFC 8463. EdDSA over Curve25519 + SHA-256 hash. Smaller public keys, faster verification. Adoption growing but not yet universal — some legacy verifiers emit dkim=permerror on Ed25519 signatures because they do not implement the algorithm.
Validity rules
- MUST appear (REQUIRED).
- MUST be one of the registered algorithm names in the IANA DKIM Algorithms registry.
- MUST match the algorithm declared in
the public-key TXT's
k=tag (defaultk=rsa;k=ed25519for Ed25519 signatures).
Failure modes
a=missing → dkim=permerror.a=value not recognized by the verifier → dkim=permerror.a=recognized but deprecated by receiver policy → dkim=policy.a=does not matchk=in the public-key TXT → dkim=permerror.
Operational guidance
Use a=rsa-sha256. Migrate
any remaining rsa-sha1 signers
immediately. Adopt
a=ed25519-sha256 only after
confirming your top inbound receivers
all support it (most modern verifiers do
as of 2024).
c= — Canonicalization
Optional (default:
simple/simple — but modern
signers always emit
relaxed/relaxed).
Format
Two slash-separated values:
c=<header-canon>/<body-canon>.
Each side may be simple or
relaxed:
simple— STRICT. No modifications to whitespace, case, or line endings. Any single-byte change breaks the signature.relaxed— FORGIVING. Collapses runs of whitespace, normalizes header-name case to lowercase, strips trailing whitespace, ignores empty lines at body end.
The four combinations
c=simple/simple— strictest; almost no message survives forwarding intact. Avoid.c=simple/relaxed— strict headers + forgiving body. Rare in practice.c=relaxed/simple— forgiving headers + strict body. Rare in practice.c=relaxed/relaxed— modern industry default. M365 and Workspace both default to this. Tolerates the common forwarder/MTA modifications without sacrificing security.
Failure mode
c=simple/simple messages
almost always produce dkim=fail after
forwarding through any list, security
gateway, or MIME-re-encoding MTA. The
common modern recommendation is
relaxed/relaxed. See
DKIM
canonicalization for the algorithmic
deep dive.
d= — Signing-domain identifier
Required. The domain that authorized the signature. THIS is the DMARC alignment anchor.
What it carries
A DNS domain name (case-insensitive). Receivers compare it against the From: header per RFC 7489 §3.1 for DMARC alignment:
- Strict alignment:
d=must equalFrom:exactly. - Relaxed alignment:
d=must share the same organizational domain asFrom:(via Public Suffix List).
Common values
d=example.com— your custom sending domain (correct).d=tenant.onmicrosoft.com— M365 falling back to tenant identity (the #1 M365 DMARC failure mode; fix is Set-DkimSigningConfig for the custom accepted domain — see DKIM for Microsoft 365).d=tenant.gappssmtp.com— Workspace falling back to tenant identity (the #1 Workspace DMARC failure mode; fix is clicking Start authentication for the custom domain — see DKIM for Google Workspace).
Validity rules
- MUST appear.
- MUST be a valid DNS domain name.
- The public-key TXT at
<s=>._domainkey.<d=>MUST resolve.
Failure modes
d=domain has no<s=>._domainkeyTXT → dkim=permerror.d=domain has the TXT but it is empty (revoked) → dkim=permerror.d=does not align with From: → dkim=pass but DMARC fails.
s= — Selector
Required. The DKIM
selector, locating the public-key TXT
record under d=.
What it carries
A short token forming the leftmost label of the public-key lookup name:
; The verifier looks up:
<s=value>._domainkey.<d=value> IN TXT
; Example: s=selector1, d=example.com
selector1._domainkey.example.com IN TXT "v=DKIM1; k=rsa; p=..."
Common per-provider naming
selector1/selector2— Microsoft 365 (two selectors for automatic rotation).google— Google Workspace (single default selector).default— cPanel and many self-hosted DKIM signers.s1/s2— SendGrid (rotation pair).mte1,k1,mta,20221208(date-based) — various ESPs.
Failure mode
Wrong-selector lookups produce dkim=permerror (NXDOMAIN at the constructed lookup name). See DKIM selectors explained for the cross-provider naming catalogue.
h= — Signed-header list
Required. Colon-separated list of header names included in the signature.
What it carries
The exact header-name sequence that
forms the signing input. The verifier
reconstructs this sequence by
concatenating the named headers (with
canonicalization applied) before
verifying b=.
; Common conservative h= list:
h=From:To:Subject:Date:Message-ID:Reply-To:Sender:MIME-Version
; Extended h= list (modern signers):
h=From:To:Subject:Date:Message-ID:Reply-To:Sender:MIME-Version:
Content-Type:Content-Transfer-Encoding:List-Unsubscribe:
Auto-Submitted:References:In-Reply-To
Required header coverage
Per RFC 6376 §5.4, From:
MUST appear in h=. A verifier MUST
ignore a signature that omits it and return PERMFAIL
(From field not signed), that is dkim=permerror.
Modern senders also
cover Subject, Date, To, Message-ID,
Reply-To, and Sender at minimum.
Repeated header names
A header name MAY appear multiple times
in h= to sign multiple
instances of that header in the message
(e.g., signing two Received headers
separately). RFC 6376 §3.5 specifies
"first instance from the bottom" matching
for each occurrence — useful to bind a
signature to specific Received hops
against a downstream MTA's later
insertions.
Null-header behaviour
A header name in h= that
does not exist in the message is
treated as a null header for signing —
its absence is signed by its absence.
Downstream insertion of that header
will then invalidate the signature.
Failure modes
h=missing → dkim=permerror.h=missingFrom:→ dkim=permerror (the signature is ignored as invalid).- Downstream MTA modified a signed header → dkim=fail.
bh= — Body hash
Required. Base64-encoded cryptographic hash of the canonicalized message body.
What it carries
The hash output (SHA-256 for
rsa-sha256 / ed25519-sha256; SHA-1 for
the deprecated rsa-sha1) of the message
body after applying the body
canonicalization side of the
c= tag. Verifiers compute
the same hash from the received body and
compare against bh=.
The two-hash design
RFC 6376 §3.7 specifies TWO independent
cryptographic operations: the body hash
(bh=) and the
signed-header-set signature (b=).
The signed-header set INCLUDES the
DKIM-Signature header itself (with
b= temporarily emptied to
avoid recursion). The bh=
is part of the signed-header set, so
b= implicitly authenticates
the body hash.
The verifier checks them sequentially: body hash first (cheap — pure SHA), then signature (expensive — public-key crypto). Body-hash mismatches abort early without consuming the public-key operation.
Failure mode
A bh= mismatch produces dkim=fail with reason "body hash did not verify" — by far the most common dkim=fail in practice. Almost always traces to a forwarder/list rewriting the body (footer injection, MIME re-encoding, line-ending normalization). See dkim=fail in the failures catalogue for the full diagnostic walkthrough.
b= — Signature
Required. Base64-encoded cryptographic signature over the canonicalized signed-header set.
What it carries
The output of the
a=<sig-alg>-<hash-alg>
cryptographic operation applied to the
canonicalized signed-header set. For
rsa-sha256 it is a 256-byte
(2048-bit-key) or 512-byte (4096-bit-key)
RSA signature, base64-encoded. For
ed25519-sha256 it is a 64-byte Ed25519
signature.
The signed-header set
The signing input is constructed by:
- Take each header named in
h=, in the order they appear inh=. - Apply the header canonicalization
side of
c=to each named header. - Concatenate the canonicalized
headers in
h=order. - Append the DKIM-Signature header
itself, canonicalized, with
b=temporarily emptied (so the value ofb=does not recursively appear in its own input). - Hash and sign the resulting input.
Failure mode
A b= mismatch produces
dkim=fail with reason "signature did
not verify". Rarer than bh= mismatch
and typically points at header
modification by an intermediate MTA —
Subject prefix added by an anti-spoof
rewriter, Reply-To inserted by a
mailing-list manager, or DKIM-Signature
header tampered with after signing.
Signature truncation in Authentication-Results
Receivers typically record the first 8
bytes of the base64 signature as
header.b= in
Authentication-Results (e.g.,
header.b=AbCdEf12). This
short prefix uniquely identifies which
DKIM-Signature instance produced the
verdict when a message carries multiple
signatures.
i= — AUID (Agent or User Identifier)
Optional. Defaults to
@<d=value>.
What it carries
A local-part + @ + domain construction identifying the agent or user on whose behalf the message was signed. Examples:
i=@example.com(default when absent ifd=example.com).i=alice@example.com— specific user.i=marketing@example.com— organizational sub-agent.
The domain part of i= MUST
equal d= OR be a subdomain
of d=. The local-part is
free-form.
Operational guidance
i= is rarely consequential.
DMARC alignment evaluates against
d=, not i=.
Omit unless you have a specific reason
to identify a sub-agent (a
marketing-platform sub-account, a
per-user signing identity for legal-hold
purposes).
Failure mode
i= with a domain that is
not a subdomain of d= →
dkim=permerror. Some receivers compare
i= local-part against
From: local-part for additional
verification, though this is
non-normative.
q= — Query method
Optional. Defaults to
q=dns/txt.
What it carries
The method by which the verifier should
retrieve the public key. The only
defined value per RFC 6376 §3.6 is
q=dns/txt — DNS TXT
lookup. Omit the tag to use this
default. Future extension methods could
add new query methods; none have been
standardized.
Failure mode
q=<unknown> →
dkim=permerror with reason "unsupported
query method".
Operational guidance
Omit. The default is correct for every deployed DKIM signer.
l= — Body-length limit (DO NOT USE)
Optional. No default.
What it carries
An integer specifying how many bytes of the body to include in the body-hash computation. The remainder of the body is UNSIGNED.
Why it exists
The tag was originally introduced so
mailing-list footers appended AFTER
signing would not break the body hash —
the signer would set
l=<body-byte-count>
so only the original body (not the
appended footer) is included in the
hash.
Why it is dangerous
The same property is a replay-attack
vector. An attacker can take a
legitimately-signed message with
l=, append arbitrary
malicious content past the signed
prefix, and the modified message still
verifies because the appended bytes are
OUTSIDE the hashed region. The
appended content can be a malicious
payload, a phishing link, or arbitrary
abuse content — all under the cover of
a legitimate DKIM signature.
RFC 6376 §8.2 itself cautions
Use of the "l=" tag might allow display of fraudulent content
without appropriate warning to end users. [...]
To avoid this attack, Signers should be extremely wary of using
this tag, and Assessors might wish to ignore signatures that use
the tag.
The modern recommendation
NEVER use l= in
production. Accept body-hash
mismatches from forwarders/lists,
configure ARC sealing for forwarder
survivability (see
ARC
explained), and let DMARC
relaxed-alignment SPF + DKIM
any-pass-wins handle the policy
decision.
Failure mode
Receivers do NOT reject l= signatures
(they verify correctly per RFC 6376) but
every modern security analysis since
2007 has cautioned against the tag.
Some strict receivers emit dkim=policy
when l= is present.
t= and x= — Signing and expiration timestamps
Both optional. Both expressed as seconds since the Unix epoch (1970-01-01T00:00:00Z).
t= — Signing timestamp
The time at which the signature was
produced. Receivers MAY use it to
detect signature replay — rejecting (or
flagging) signatures with
t= older than some
threshold (e.g., 7 days). The tag is
INFORMATIONAL — verifiers do not
require it but its presence helps
receiver-side defenses.
x= — Expiration timestamp
The time after which the signature MAY be considered
invalid. Verifiers checking x= ignore
signatures whose x= is past the current
time and return PERMFAIL (signature expired). RFC
6376 permits this but does not require it — a
receiver MAY reject an expired signature.
Operational guidance
- Include
t=— gives receivers replay-detection material without committing to hard expiration. - Avoid
x=— introduces clock-skew failure modes. If signer and receiver disagree by more than thex=window, legitimate mail fails dkim=policy with reason "signature expired". - If you must use
x=, set generously: 24+ hours pastt=minimum. Make sure your signer's clock is NTP-synchronized.
Failure modes
x=past current time → dkim=policy with reason "signature expired".t=older than receiver replay-detection threshold → dkim=policy with reason "signature too old".
z= — Copied headers (diagnostic-only)
Optional. No default.
What it carries
Pipe-separated copies of selected
message headers at the time of signing,
encoded for transport (whitespace
replaced with `=20` etc. per
RFC 6376 §3.5 DKIM-Quoted-Printable
encoding). The tag is diagnostic only —
verifiers do NOT use z=
for verification, just for failure
forensics.
Use case
When a verifier emits dkim=fail because
a signed header was modified
downstream, z= lets the
operator compare the signed snapshot
against the current message header to
identify which header was tampered.
Operational guidance
Generally omit. z= bloats
the DKIM-Signature header substantially.
Include only if you actively
investigate downstream-modification
incidents and need the forensic snapshot.
Detecting malformed DKIM-Signature headers
The most common malformations operators encounter when auditing signed mail:
1. Missing required tag
Any of v=, a=,
d=, s=,
h=, bh=,
b= absent. Verdict:
dkim=permerror. Cause: signer
misconfiguration or header truncation.
2. Inconsistent algorithm declarations
a= declares one algorithm
but the public-key TXT's
k= tag declares a
different one. Verdict: dkim=permerror.
3. Invalid base64 in b= or bh=
A character outside the base64 alphabet appears in either tag, or the length does not match the algorithm's signature/hash output size. Verdict: dkim=permerror.
4. i= domain not under d=
i=user@foreign.com with
d=example.com is invalid
— the i= domain must equal or be a
subdomain of d=. Verdict: dkim=permerror.
5. From: header missing from h=
Per RFC 6376 §5.4, From MUST be signed. §6.1.1 tells the verifier to ignore a signature without it. Verdict: dkim=permerror.
6. l= present in production
Not technically malformed but a red flag. The replay-attack risk should prompt an investigation: who set the tag, why, and can it be removed?
Diagnostic tools
The
Email
Header Analyzer
(header_analyzer.php)
parses DKIM-Signature headers
tag-by-tag via
includes/EmailHeaderParser.php
and surfaces all malformation patterns
above. The
Domain
Security Checker validates the
public-key TXT side of the equation —
confirming the d= and s= referenced
by a DKIM-Signature actually resolve
to a valid public-key record.
Frequently asked questions
- What does the DKIM-Signature header look like?
- A typical DKIM-Signature header is one wrapped logical line containing tag=value pairs separated by semicolons. Example: DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=selector1; h=From:To:Subject:Date:Message-ID; bh=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=; b=AbCdEf1234567890... The required tags are v= (version, always 1), a= (algorithm, modern senders use rsa-sha256), d= (signing domain), s= (selector), h= (signed-header list), bh= (body hash base64), and b= (signature base64). Optional tags are c= (canonicalization, default simple/simple; modern senders use relaxed/relaxed), i= (AUID), q= (query method, defaults to dns/txt), l= (body-length limit), t= (signing timestamp), x= (expiration timestamp), and z= (copied headers).
- What is the difference between the b= and bh= tags?
- The b= tag (base64) holds the cryptographic signature of the SIGNED-HEADER set including a representation of the DKIM-Signature header itself (with the b= value temporarily empty for the signing computation). The bh= tag (also base64) holds a SEPARATE hash of the message body. The verifier checks both: it computes the body hash and compares against bh=, and it reconstructs the signed-header set and verifies the signature against b=. Most dkim=fail verdicts in practice come from a bh= mismatch — a forwarder rewrote the body so the computed hash no longer equals the published bh=. dkim=fail from a b= mismatch is rarer and usually means a header was modified after signing (e.g., a Subject prefix added by an anti-spoof gateway). The two-hash design is intentional: it lets receivers verify the body hash cheaply (without performing public-key cryptography) before committing to the expensive signature verification.
- What does v=1 mean in DKIM-Signature?
- v=1 is the protocol version per RFC 6376 §3.5. There has never been a v=0 or v=2 in production DKIM — the spec was published as v=1 from the start and no subsequent algorithm revisions (RFC 6376 errata, RFC 8301 algorithm update, RFC 8463 Ed25519 algorithm addition) have bumped the version. As an operator you must always emit v=1 and verifiers MUST reject signatures lacking it. The tag is required per §3.5 and is conventionally the first tag in the header. If you see a DKIM-Signature without v=1 it is malformed and verifiers will emit dkim=permerror.
- What is the h= tag and which headers should it cover?
- The h= tag lists the names of message headers covered by the signature, colon-separated and case-insensitive. The verifier reconstructs the signed-header set by concatenating these exact headers in the listed order (with canonicalization applied per the c= tag). The most-commonly signed headers per the RFC 6376 Appendix C example: From (REQUIRED — RFC 6376 §5.4 mandates From signing), Subject, Date, To, Message-ID, Reply-To, Sender, MIME-Version, Content-Type, Content-Transfer-Encoding. Modern signers also include List-Unsubscribe (RFC 8058 one-click), Auto-Submitted, References, In-Reply-To, and Resent-* headers when applicable. A header named in h= that does not exist in the message is treated as a null header for the signing computation. If h= omits From, the verifier MUST ignore the signature and return PERMFAIL (From field not signed); for other important headers a receiver may emit dkim=policy when its local policy requires them signed.
- What is the difference between c=simple/simple and c=relaxed/relaxed?
- c= specifies the canonicalization algorithm pair: c=<header-canon>/<body-canon>. simple is the strict mode — no whitespace or case modifications allowed; any single-byte change anywhere breaks the signature. relaxed is the forgiving mode — collapses runs of whitespace, normalizes case in header names, strips trailing whitespace, and ignores empty lines at the end of bodies. The modern industry default is c=relaxed/relaxed because forwarders, mailing lists, and security gateways routinely perform whitespace normalization that simple cannot tolerate. Microsoft 365 and Google Workspace both default to relaxed/relaxed. Use simple/simple only for closed-loop signing where you control every hop in the delivery path. The full canonicalization tradeoff is in help_dkim_canonicalization.php.
- When should I use the t= and x= timestamp tags?
- The t= tag is the signature timestamp (seconds since Unix epoch); the x= tag is the signature expiration timestamp. Both are optional. Receivers MAY use t= to detect signature replay (refusing signatures with t= older than some threshold) and MAY use x= to refuse signatures past their declared expiration. The operational tradeoff: including t= and x= reduces replay-attack dwell time (see help_dkim_replay_attacks.php) but introduces clock-skew failure modes — if your signer and the receiver disagree on the current time by more than the expiration window, legitimate messages fail dkim=policy. Modern recommendation: include t= but not x=. The t= alone gives receivers replay-detection material without committing to a hard expiration. If you do use x=, set it generously (24 hours minimum past t=) and ensure your signer clock is NTP-synchronized.
- What is the l= tag and why is it dangerous?
- The l= tag is the body-length-limit per RFC 6376 §3.5 — it tells the verifier "only hash the first N bytes of the body when computing bh=". Operators sometimes add l= so mailing-list footers appended after signing do not break the body hash. But l= is dangerous as a replay-attack vector: an attacker can take a legitimately-signed message with l=, append arbitrary malicious content past the signed prefix, and the modified message still verifies because the appended bytes are outside the hashed region. The canonical industry advice (RFC 6376 §8.2 itself + every security analysis since) is: NEVER USE l= in production. Set the body completely without partial-hash, accept the body-hash-mismatch failures from forwarders, and let DMARC alignment handle the policy decision via SPF + relaxed alignment. If you do use l=, set it to the full body length and remove the tag — at which point it adds nothing and you might as well omit it.
- What does the i= tag mean and when should I set it?
- The i= tag is the Agent or User Identifier (AUID) per RFC 6376 §3.5 — typically a local-part @ d= construction like i=alice@example.com or @example.com. It identifies the SPECIFIC user or agent on whose behalf the message was signed, distinct from the d= signing-domain identifier. When absent, i= defaults to @<d=value>. The AUID is OPTIONAL and rarely consequential — DMARC alignment evaluates against d=, not i=. Practical guidance: omit i= unless you have a specific reason to identify a sub-agent (a marketing-platform sub-account, a per-user signing identity). When emitted, the i= local-part must match what the verifier expects per any local policy that constrains AUIDs. Some receivers compare i= against the From: header local-part for additional verification, though this is non-normative.
Parse a DKIM-Signature now
DMARC Analyzer parses every DKIM-Signature
header in uploaded messages and aggregate
reports via
includes/EmailHeaderParser.php,
surfacing the tag-by-tag breakdown on the
Header Analyzer view. The
Email
Header Analyzer at
header_analyzer.php accepts
any raw message headers and produces a
parsed view of every DKIM-Signature
instance — full tag inventory, base64
integrity checks, and red-flag
indicators for malformed tag values.
The
Domain
Security Checker validates the
public-key TXT side: confirms the
d= and s=
referenced by any DKIM-Signature
actually resolve to a valid record.