What does each tag in a DKIM-Signature header mean?
A DKIM-Signature header is a list of tag=value pairs: v= version, a= algorithm, d= signing domain, s= selector, h= signed headers, bh= body hash and b= the signature. c=, i=, l=, t= and x= are optional. For DMARC, d= is the tag that matters, because it must align with the From domain.
On this page 6 sections
Every DKIM-signed message carries a DKIM-Signature header, and almost nobody reads it. That's a shame, because the header tells you exactly who signed the message, what they signed, and why a check passed or failed. This guide decodes each tag from RFC 6376, using an annotated example. If you're new to DKIM, start with what DKIM is.
What does a DKIM-Signature header look like?
Here's a typical signature, as you'd see it in a received message. The domain and values are illustrative and shortened:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com;
s=s202610; t=1791633600; x=1792238400; i=@example.com;
h=from:to:cc:subject:date:message-id:reply-to:mime-version:content-type:from;
bh=Xq2mR8vT0bN4kL7pW1sZ9cF3hJ6dY5gA2eU8iO0rT4w=;
b=Kq3vT1ZbWm0e9yQ0dD2cJr4pX8sN5uFq7LzR0aBcD1eFgHiJkLmNoPqRsTuVwXyZ
9fG2hJ3kL4mN5oP6qR7sT8uV9wX0yZ1aB2cD3eF4gH5iJ6kL7mN8oP9qR0sT1uV2w==
The header is a list of tag=value pairs separated by semicolons, in any order. Line breaks and indentation are just folding; they mean nothing. RFC 6376 section 3.5 says unrecognized tags "MUST be ignored", which is what lets the format grow over time.
Read it in three groups:
- Who signed:
d=,s=andi=. - What was signed, and how:
a=,c=,h=,l=andbh=. - The proof and its lifetime:
b=,t=andx=.
What does each DKIM tag mean?
| Tag | Name in RFC 6376 | Required? | In the example | What it tells you |
|---|---|---|---|---|
v= |
Version | Yes | 1 |
Must be 1 |
a= |
Algorithm | Yes | rsa-sha256 |
How the signature was made |
b= |
Signature data | Yes | Kq3vT1... |
The signature itself, base64 |
bh= |
Body hash | Yes | Xq2mR8... |
Hash of the canonicalized body |
c= |
Canonicalization | No (default simple/simple) |
relaxed/relaxed |
How tolerant of small changes the signature is |
d= |
Signing Domain Identifier (SDID) | Yes | example.com |
Who takes responsibility |
s= |
Selector | Yes | s202610 |
Which public key to fetch |
h= |
Signed header fields | Yes | from:to:... |
Which headers are protected |
i= |
Agent or User Identifier (AUID) | No (default @ plus d=) |
@example.com |
Optional finer-grained identity |
l= |
Body length count | No (default whole body) | Absent | How much of the body is covered |
t= |
Signature timestamp | No (recommended) | 1791633600 |
When it was signed |
x= |
Signature expiration | No (recommended) | 1792238400 |
When it stops being valid |
Two rarer tags exist: q=, whose only defined value is the default dns/txt, and z=, which copies the original headers for diagnostics. You'll seldom see either.
v= and a=: version and algorithm
v=1 is the only version. For a=, RFC 6376 allowed rsa-sha1 and rsa-sha256, but RFC 8301 later ruled that "rsa-sha1 MUST NOT be used for signing or verifying". So rsa-sha256 is the normal value. You may also see ed25519-sha256, added by RFC 8463, usually alongside an RSA signature.
d= and s=: who signed, and where the key lives
d= is the domain claiming responsibility. RFC 6376 says it "MUST correspond to a valid DNS name under which the DKIM key record is published." s= picks one key among several under that domain. The verifier joins them to build a DNS query:
s202610._domainkey.example.com TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
If that lookup fails, verification fails with "no key for signature". Our guide to finding a DKIM selector covers this lookup in detail, and the DKIM Checker runs it for you.
h=: which headers are protected
h= lists the header fields fed into the signature, in order. RFC 6376 section 5.4 says the From header "MUST be signed", and that signing "Date, Subject, Reply-To, Sender, and all MIME header fields are highly advised." Headers that aren't listed can be changed or added freely without breaking the signature.
Notice that from appears twice in the example. That's deliberate. A signer may list a header more times than it occurs, and a missing instance is treated as empty. The RFC explains that this lets the signer "explicitly assert the absence of a header field; if that header field is added later, the signature will fail." Listing from one extra time stops anyone from adding a second From header after signing. This is often called oversigning.
c=, bh= and b=: canonicalization and the two hashes
c= names the header and body canonicalization, written header/body. relaxed tolerates whitespace changes and line rewrapping; simple tolerates almost nothing. If only one value is given, RFC 6376 says it applies to the header and simple is used for the body, so c=relaxed means relaxed/simple.
bh= is the hash of the canonicalized body. b= is the actual signature, computed over the signed headers plus the DKIM-Signature header itself with b= empty. A receiver checks both: a bh= mismatch means the body changed, and a b= failure with a good body hash means a signed header changed or the key is wrong. Our guide to DKIM "body hash did not verify" walks through each failure.
i=: the optional user identity
i= defaults to @ followed by the d= domain. If present, its domain "MUST be the same as, or a subdomain of" d=. Some platforms put the sender's full address here. RFC 6376 cautions that reliance on bindings between these identifiers and other identities "should be strictly limited", and DMARC doesn't use i= at all.
t= and x=: signing time and expiry
Both are Unix timestamps in seconds. In the example, t=1791633600 is 10 October 2026 at 12:00 UTC, and x= is seven days later. RFC 6376 says signatures "MAY be considered invalid" after x=, and x= must be greater than t= if both are present. The RFC also notes that x= "is not intended as an anti-replay defense".
What does d= mean for DMARC alignment?
DKIM on its own proves only that some domain signed the message. RFC 9989, the current DMARC standard, explains why that isn't enough: "a message can bear a valid signature from any domain, even one used by a bad actor." So DMARC requires the d= domain to align with the domain in the visible From address.
| From address | DKIM d= |
Relaxed alignment (default) | Strict (adkim=s) |
|---|---|---|---|
you@example.com |
example.com |
Pass | Pass |
you@example.com |
mail.example.com |
Pass | Fail |
you@example.com |
example.onmicrosoft.com |
Fail | Fail |
you@example.com |
sendgrid.net |
Fail | Fail |
The last two rows are the classic trap: DKIM shows pass, but DMARC fails because the signature belongs to someone else's domain. The fix is custom-domain signing at each service. Our guide to relaxed vs strict alignment covers the alignment rules in depth.
A message can carry several signatures, and RFC 9989 says it passes DMARC "if any DKIM-Authenticated Identifier aligns with the Author Domain." One good aligned signature is enough.
Why is the l= tag a risk?
l= tells the verifier how many bytes of the body the signature covers. It was meant to help signatures survive mailing lists that append footers. The problem is that anything after that byte count is unsigned, so anyone who gets hold of a signed message can add content to it and the signature still verifies.
RFC 6376 section 8.2 is blunt. Using l= "might allow display of fraudulent content without appropriate warning to end users", and appended content can "completely replace the original content in the end recipient's eyes." It advises signers to "be extremely wary of using this tag" and says receivers "might wish to ignore signatures that use the tag."
Google goes further. Gmail Help says: "Do not use the DKIM length tag (l=) in message headers. This tag makes messages vulnerable to spoofing."
If you see l= in your own outgoing signatures, turn it off in your mail platform's DKIM settings or ask your provider how to remove it.
How do you read a DKIM signature in a received message?
- Open the full headers. In Gmail, use More > Show original. In Outlook, use View > View message details. Our guide on how to read email headers covers other clients.
- Find every
DKIM-Signatureheader. They're near the top. Note each one'sd=ands=. - Find your receiving system's
Authentication-Resultsheader. RFC 8601 reports DKIM tags with theheader.prefix, soheader.d=example.comis thed=of the signature that was checked. Some receivers also showheader.s=andheader.i=. - Match results to signatures. If there are two signatures, you may see two
dkim=results. Adkim=passforheader.d=sendgrid.netand adkim=failforheader.d=example.comtells you which one broke. - Compare
d=with the From domain. If no passing signature aligns, DMARC can't pass on DKIM. - Check for
l=and weak algorithms.rsa-sha1orl=on your own mail is something to fix.
RFC 8601 defines the DKIM results you'll meet: pass, fail, none (not signed), neutral (syntax errors or couldn't be processed), policy, temperror (for example, a temporary DNS problem) and permerror (for example, a required header missing).
If you'd rather not decode it by hand, paste the headers into the Email Header Analyzer. It runs in your browser, so nothing is uploaded.
What Microsoft 365 signatures look like
Microsoft Learn shows a signature from a custom domain with d=contoso.com and s=selector1, and says to check d= for the signing domain and s= for the selector. Microsoft says that, until you configure it, no DKIM signing occurs for outbound mail from custom domains. So if mail from your own domain arrives with no Microsoft signature at all, custom DKIM isn't set up for it. Our DKIM setup guide for Microsoft 365 and Google Workspace fixes that.
Next steps
- Send a test message from each system that mails as your domain, and check that every signature has a
d=that aligns with your From domain. - Look up each selector with the DKIM Checker to confirm the key exists and is at least 2048 bits.
- Remove
l=from any signer that adds it. - Check your DMARC record with the DMARC Checker so aligned DKIM actually protects your domain.
- For provider-specific selectors and records, see DKIM records by provider.
Browse our other SPF, DKIM and DMARC guides.
Frequently asked questions
Which DKIM tags are required?
RFC 6376 marks seven tags as required: v= (version), a= (algorithm), b= (signature), bh= (body hash), d= (signing domain), h= (signed header fields) and s= (selector). The others, c=, i=, l=, q=, t=, x= and z=, are optional. A signature missing a required tag can't be verified.
What is the d= tag in DKIM?
d= is the Signing Domain Identifier: the domain that takes responsibility for the message. The verifier combines it with the selector to find the public key at selector._domainkey.domain. DMARC passes on DKIM only when d= aligns with the domain in the visible From address, either exactly or by sharing the same organizational domain.
Is the DKIM l= tag safe to use?
No. l= tells verifiers to check only the first part of the body, so anyone can append content after it without breaking the signature. RFC 6376 warns that it 'might allow display of fraudulent content', and Google's Gmail Help says not to use it because it makes messages vulnerable to spoofing.
Why does a message have more than one DKIM-Signature header?
Each system that signs adds its own. A newsletter platform might sign with its own domain and with yours, and a mailing list or gateway can add another. Receivers check each one separately. RFC 9989 says DMARC passes if any valid signature has a d= that aligns with the From domain.
What does the bh= tag contain?
bh= is a base64 hash of the message body after canonicalization. The verifier recomputes the hash from the body it received and compares. If anything in the body changed after signing, such as a footer or disclaimer, the hashes differ and you see a 'body hash did not verify' failure.
Sources
- RFC 6376: DomainKeys Identified Mail (DKIM) Signatures, sections 3.5, 5.4 and 8.2
- RFC 8301: Cryptographic Algorithm and Key Usage Update to DKIM
- RFC 8601: Message Header Field for Indicating Message Authentication Status
- RFC 9989: DMARC, section 4.4.1 (DKIM-Authenticated Identifiers)
- Gmail Help: Check if your Gmail message is authenticated
- Microsoft Learn: How to use DKIM for email in your custom domain
See how your domain measures up
Run a free Email Health Check: MX, SPF, DKIM, DMARC and transport security in one scan, with the raw records shown. No sign-up.
Keep reading
DKIM "body hash did not verify" and other DKIM failures: causes and fixes
Fix DKIM failures: body hash did not verify, signature did not verify, no key for signature and short keys. Causes from mailing lists to broken TXT records.
9 min read SPF, DKIM & DMARCWhat is DKIM, and how does a DKIM signature work?
What DKIM is, how a signature is created and verified, what d=, s=, h=, bh= and b= mean, how selectors, key length and Ed25519 work, and why DKIM fails.
10 min read SPF, DKIM & DMARCDMARC alignment: relaxed vs strict, and what adkim and aspf do
How DMARC alignment works, what adkim and aspf do, how the organizational domain is found, a pass/fail examples table, and when strict alignment is worth it.
9 min read