DKIM Signature Check: Read the DKIM-Signature Header and Look Up the Selector
Every mail you open during triage carries a DKIM-Signature: header — a
wall of base64 that most analysts scroll past. That is a mistake: the tags in
that header tell you who cryptographically vouched for the message, which
parts of it they signed, and where in DNS you can verify the key still exists.
This article decodes the header field by field, from a SOC analyst's
perspective — reading and verifying, not setting up signing for your own
domain.
Pre-requisites
- A raw mail with full headers — for Outlook
.msgexports, convert first: Open Outlook .msg Files on Linux digon your analysis machine (packagebind9-dnsutilson Debian/Ubuntu)- The companion articles: SPF and DMARC
Examples use RFC 2606 reserved domains (example.com, example.org) —
substitute the values from your case.
What DKIM Is — and What It Is Not
DKIM (DomainKeys Identified Mail, RFC 6376) lets a mail server attach a digital signature to each outgoing message. The sending domain holds the private key; the matching public key is published in DNS. Any receiver can fetch that key and verify two things:
- Origin — a server holding the domain's private key really did handle this message.
- Integrity — the signed headers and the body have not been modified since signing.
Two misconceptions to clear up straight away:
- DKIM is signing, not encryption. The body remains plaintext; anyone on the path can read it. RFC 6376 states explicitly that no attempt is made to include encryption in the mechanism. DKIM answers "was this tampered with, and who vouches for it" — not "who can read it".
dkim=passdoes not mean the mail is legitimate. Any spammer can sign flawlessly with their own throwaway domain. The pass only tells you the signing domain (d=) vouches for the message — whether that domain has any business appearing in the user-visibleFrom:is what DMARC alignment decides.
Reading the DKIM-Signature Header
A typical signature, unfolded for readability:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.org;
s=mail2026; t=1783958400; h=from:to:subject:date:message-id;
bh=Fn1L7ItY0dUYnRp9CTLdX3jq4hQzGm2tK0aQeVYw9uE=;
b=KxQ2mP8vRw...
The tags that matter in triage:
| Tag | Meaning | Analyst notes |
|---|---|---|
v= |
Version, always 1 |
|
a= |
Signing algorithm | rsa-sha256 is standard; ed25519-sha256 exists (RFC 8463) |
d= |
Signing domain (SDID) — the domain taking responsibility | The single most important tag; compare against the From: domain |
s= |
Selector — picks which key under d= was used |
You need s= + d= for the DNS lookup below |
h= |
Colon-separated list of signed header fields | Headers not listed can be altered without breaking the signature |
bh= |
Hash of the canonicalised body | |
b= |
The signature itself (base64) | |
c= |
Canonicalisation, e.g. relaxed/relaxed |
relaxed tolerates whitespace changes; simple tolerates almost none |
t= / x= |
Signing timestamp / expiry (Unix time) | |
i= |
Identity of the signing agent or user | Optional; defaults to @ + the d= domain |
The key insight sits in d=: RFC 6376 does not require the signing domain
to match the From: header — that check is deliberately left to policy layers
above DKIM. A newsletter sent through an email service provider is routinely
signed with d=example.org (the ESP) while the From: shows example.com
(the customer). Legitimate, and common. It only becomes an anti-spoofing
signal once DMARC demands alignment between d= and the From: domain.
Also check h= when a mail smells manipulated: only the listed headers are
covered by the signature. Subject: or From: missing from h= means those
fields could have been rewritten after signing without invalidating it.
Look Up the Public Key with dig
The verifier builds the DNS name from the two tags you just read:
<selector>._domainkey.<domain> — for the example above,
s=mail2026 and d=example.org:
The key record's own tags: v=DKIM1 (version), k= (key type, defaults to
rsa), and p= — the base64 public key. An empty p= value means the key
has been revoked; the domain is telling receivers that signatures made with
this selector must no longer verify.
What this lookup gives you as an analyst:
- Key exists — the signature was at least made against real, current infrastructure of that domain.
- NXDOMAIN / no record — the selector does not exist (typo, retired key,
or a fabricated
DKIM-Signature:header pasted in by the sender). - Empty
p=— key deliberately revoked.
What it does not give you: the actual verification. The signature covers the
exact canonicalised bytes of the message, so dig alone cannot tell you
whether b= is valid — your mail gateway did that and recorded the result
(next section). To re-verify offline, the dkimpy project ships a
dkimverify tool (Debian/Ubuntu package python3-dkim) that reads an .eml
from stdin:
Reading the Verdict in Authentication-Results
Your gateway records its verification in the Authentication-Results: header
(RFC 8601). Microsoft 365
syntax:
| Verdict | Meaning for the analyst |
|---|---|
dkim=pass |
Signature verified; header.d names the domain that vouches. Now ask: does that domain fit the From:? |
dkim=fail |
A signature was present but did not verify — the reason (e.g. body hash did not verify) is usually appended |
dkim=none |
Message was not signed at all |
dkim=fail is not automatically malicious
Anything that rewrites the message after signing breaks the signature:
mailing lists adding footers, gateways injecting "[EXTERNAL]" banners or
rewriting links, even aggressive line re-wrapping when c=simple is used.
A fail on a mailing-list mail is background noise; a fail on a direct
wire-transfer request is worth your attention. Context decides.
Remember that senders can forge Authentication-Results: headers — only
trust the instance stamped by your own infrastructure (the authserv-id
after the colon tells you who wrote it).
Where DKIM Fits
DKIM gives you an authenticated domain (d=) and message integrity — but no
statement about the From: header the user sees, and no policy about what
receivers should do when verification fails. SPF
has the same blind spot from a different angle: it authenticates the envelope
sender instead. DMARC is the layer that ties
either result to the visible From: domain and attaches a policy — read that
next for the complete picture.
Check It Yourself
Pull the selector and domain out of any mail's DKIM-Signature: header and
confirm the key is published:
A v=DKIM1 record with a non-empty p= confirms the key is live. No answer
means the selector never existed or has been removed; an empty p= means it
was revoked.