DKIM, Explained Without The Hand-Waving
The Mythic Intel Team · Oct 19, 2025 · 7 min read
DKIM, defined in RFC 6376, lets a domain attach a cryptographic signature to an outgoing message so that any receiver can verify the message was authorized by that domain and was not altered in transit. The sender signs a chosen set of headers plus a hash of the body with a private key it keeps secret; the receiver fetches the matching public key from a DNS TXT record and checks the signature. If the math holds, the receiver knows the signing domain stands behind the message.
The reason DKIM matters more than it first appears is durability. An SPF check validates the connecting IP against the sending domain's authorized hosts, and that check breaks the moment the message is forwarded by an intermediary whose IP is not in the SPF record. A DKIM signature travels with the message, so it can still verify after a forward, as long as the signed content was not modified.
What actually gets signed
DKIM produces two hashes. One is over the message body, the other is over a selected list of header fields. Both are computed after canonicalization, which is the step that normalizes whitespace and folding so that benign in-transit changes do not break an otherwise valid signature.
The signer writes its work into a DKIM-Signature: header on the message itself. A real one looks like this:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=mail2024; t=1718200000;
h=from:to:subject:date:message-id;
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
b=Cb9z6kP7H0o3X1mq...AB1cdEf2GhIjKlMnOpQrStUvWxYz0123456789==
Reading the tags in order:
v=1is the DKIM version. It is always 1.a=rsa-sha256is the signing algorithm. RSA with SHA-256 is the common choice; Ed25519 is also defined and increasingly used.c=relaxed/relaxedis the canonicalization for header and body respectively.relaxedtolerates whitespace and header-folding changes;simpletolerates almost nothing. Most senders use relaxed because mail systems routinely rewrap headers.d=example.comis the SDID, the signing domain identifier. This is the domain the signature asserts responsibility for, and it is the value DMARC aligns against the From domain.s=mail2024is the selector. It names which key to fetch under that domain, which is how you rotate keys and run multiple signers without collision.t=is the signing timestamp.h=from:to:subject:date:message-idis the ordered list of headers that were signed. From is mandatory. A header not listed here is not protected, so a receiver knows exactly what the signature covers.bh=is the base64 body hash. The receiver recomputes the canonicalized body hash and compares it to this value. A single changed byte in the signed body fails the match.b=is the signature itself, the base64 RSA signature over the canonicalized signed headers (including theDKIM-Signatureheader withb=emptied). This is what the public key verifies.
Where the public key lives
The verifier needs the public key, and DKIM puts it in DNS under a name built from the selector and the fixed _domainkey label: selector._domainkey.signingdomain. For the signature above, that is mail2024._domainkey.example.com. A real published record:
mail2024._domainkey.example.com. IN TXT (
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ"
"C7Vn3...rest-of-base64-public-key...QIDAQAB" )
The tags here:
v=DKIM1identifies the record as a DKIM key record.k=rsais the key type.p=is the base64-encoded public key. Ifp=is empty, the key has been revoked and any signature using this selector must fail.
Because the lookup name includes the selector, you can publish mail2024 and mail2025 side by side, sign new mail with the new selector, and retire the old key by emptying its p= only after no in-flight mail still references it. That is the whole key-rotation mechanism, and it is why a selector is not optional decoration.
Why DKIM survives a forward when SPF does not
Consider a mailing list or a simple forwarder. A subscriber's address at lists.example.org receives the message and re-sends it to the final recipient. At the receiving MX:
- SPF evaluates the connecting IP, which is now the forwarder's IP, against the SPF record of the bounce domain. The forwarder is almost never listed there, so SPF fails. This is the classic "forwarding breaks SPF" problem.
- DKIM re-fetches the public key for the original
d=domain and recomputes the body and header hashes. If the forwarder did not rewrite a signed header or alter the body, the signature still verifies, and the original domain's authentication holds.
The caveat is content modification. A list that appends a footer or rewrites the Subject changes the signed body or a signed header, which breaks DKIM too. That failure mode is exactly what ARC and list-side signing were designed to address, but the base point stands: SPF is tied to the transport path and DKIM is tied to the message, so DKIM is the authentication that can pass through an honest forward.
A practical verification of your own setup is to send a message to an account you control and read the Authentication-Results header the receiver stamps:
Authentication-Results: mx.recipient.example;
dkim=pass header.d=example.com header.s=mail2024;
spf=pass smtp.mailfrom=bounce.example.com;
dmarc=pass (p=reject) header.from=example.com
dkim=pass header.d=example.com confirms the signature verified against the public key and the body was intact.
Saying it out loud
In an interview, the crisp version is: "DKIM signs a body hash and a chosen set of headers with a private key, publishes the public key at selector._domainkey.domain, and the receiver verifies with that key. The d= is the domain on the hook, the s= selector picks the key and enables rotation, bh= is the body hash and b= is the signature. It survives forwarding because it is bound to the message, not the connecting IP, which is the failure mode that sinks SPF." If you can also note that DMARC aligns the d= domain against the visible From, you have shown you understand where DKIM sits in the larger authentication stack.