Skip to content

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 .msg exports, convert first: Open Outlook .msg Files on Linux
  • dig on your analysis machine (package bind9-dnsutils on 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:

  1. Origin — a server holding the domain's private key really did handle this message.
  2. 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=pass does 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-visible From: 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:

dig +short txt mail2026._domainkey.example.org
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC..."

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:

sudo apt-get install python3-dkim
dkimverify < suspicious-mail.eml

Reading the Verdict in Authentication-Results

Your gateway records its verification in the Authentication-Results: header (RFC 8601). Microsoft 365 syntax:

Authentication-Results: ...; dkim=pass (signature was verified)
 header.d=example.org; ...
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:

dig +short txt mail2026._domainkey.example.org

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.