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 (with b= temporarily emptied for the signing computation). Most dkim=fail comes from bh= 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 via x= trades replay defense for clock-skew failure modes — set generously (24+ hours past t=) or omit.
  • h= must cover From. RFC 6376 §5.4 mandates signing the From: header; a verifier must ignore a signature whose h= omits it (dkim=permerror). Modern signers cover From, Subject, Date, To, Message-ID, Reply-To, Sender, MIME-Version at minimum.
  • d= drives DMARC alignment, not i=. The DMARC engine compares the d= tag to the From: header per RFC 7489 §3.1 — the M365 onmicrosoft.com gotcha and Workspace gappssmtp.com gotcha are both d= 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 (default k=rsa; k=ed25519 for 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 match k= 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 equal From: exactly.
  • Relaxed alignment: d= must share the same organizational domain as From: (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=>._domainkey TXT → 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= missing From: → 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:

  1. Take each header named in h=, in the order they appear in h=.
  2. Apply the header canonicalization side of c= to each named header.
  3. Concatenate the canonicalized headers in h= order.
  4. Append the DKIM-Signature header itself, canonicalized, with b= temporarily emptied (so the value of b= does not recursively appear in its own input).
  5. 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 if d=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 the x= window, legitimate mail fails dkim=policy with reason "signature expired".
  • If you must use x=, set generously: 24+ hours past t= 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.

Sign up — Free plan Parse a DKIM-Signature header