DKIM keys, selectors and signatures

How DKIM signing and verification work, every tag in the key record and the signature header, key lengths, rotation, and the l= tag you should never use.

DKIM is a signature, not a list. The sender signs selected headers and a hash of the body with a private key, attaches the result as a DKIM-Signature header, and publishes the matching public key in DNS. A receiver reads which key to use from the header itself and verifies.

To look up a live key, use the DKIM checker.

How verification works

  1. The receiver reads the DKIM-Signature header and takes two values from it: d= (the signing domain) and s= (the selector).
  2. It queries TXT at selector._domainkey.domain to fetch the public key.
  3. It recomputes the body hash and the signed-header hash using the canonicalization named in c=.
  4. It verifies the signature against the public key. Pass or fail.

The consequence worth internalising: the message tells the receiver which key to use. There is no way to ask a domain which selectors it uses, which is why a checker can only guess at common ones.

The key record

Published at selector._domainkey.yourdomain as a TXT record.

Tag Purpose Notes
v Version. DKIM1 if present. Optional, but must be first if used
k Key type: rsa or ed25519. Defaults to rsa
p The public key, base64. Required. Empty means revoked
h Acceptable hash algorithms. Rarely set; sha256 in practice
s Service type this key is valid for. email or *. Rarely set
t Flags: y test mode, s no subdomain use. See below
n Free-text note for humans. Ignored by verifiers

p, and what empty means

p= with nothing after it is an explicit revocation — a statement that this selector's key is no longer valid. It is the correct way to retire a key, and a common outage when it is published while the platform is still signing with that selector.

t=y and t=s

t=y means "test mode": receivers should verify but not treat a failure as meaningful. It exists for rollout, and it is regularly left on for years, quietly rendering the signature advisory. If a signature verifies and DMARC still is not behaving, check for it.

t=s forbids the key being used for subdomains. Harmless if you mean it, and an obscure cause of failure if a platform signs as a subdomain.

The DKIM-Signature header

The other half. These tags are on the message, not in DNS.

Tag Purpose
d= The signing domain. This is what DMARC checks alignment against
s= The selector — which key to fetch
h= Colon-separated list of headers that were signed, in order
bh= Hash of the canonicalized body
b= The signature itself
a= Algorithm, e.g. rsa-sha256 or ed25519-sha256
c= Canonicalization for header and body, e.g. relaxed/relaxed
l= Body length that was signed. Avoid — see below
t= / x= Signature timestamp and expiry

h= is what actually got protected

Only the headers listed in h= are covered. Anything not listed can be changed or added in transit without breaking the signature. From must be included, and a sensible list also covers Subject, Date, To and Message-ID.

A signature that covers only From is technically valid and protects almost nothing interesting.

c= canonicalization

simple requires the content to survive byte-for-byte; relaxed tolerates whitespace and header-folding changes that normal mail infrastructure makes constantly. relaxed/relaxed is what almost everyone uses, because simple breaks on innocuous rewriting.

l= is a footgun

l= says "only the first N bytes of the body are signed". Anything appended after that point is unsigned — and still arrives, inside a message carrying a valid signature. An attacker who can inject content can append whatever they like and the signature still verifies.

There is no good reason to use it. If you see it in your own outbound mail, ask your platform why.

Key length and algorithm

Choice Status
RSA 1024 Verifies widely, below current guidance. Acceptable only if nothing better is available
RSA 2048 The sensible default
RSA 4096 Often will not fit in DNS conveniently, and buys little over 2048
Ed25519 Short keys, modern, but support is not universal — publish alongside an RSA key rather than instead of one

The 255-character problem

A single TXT string cannot exceed 255 characters, and a 2048-bit RSA key does. The record must be published as several quoted strings, which the resolver concatenates back into one value:

"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7Zk..." "...remainder of the key..."

Behaviour varies by DNS provider. Some split automatically, some require you to do it, and some silently truncate — producing a key that looks published and never verifies. If a 2048-bit key fails while a 1024-bit one worked, this is almost always why.

Rotation

Rotating keys limits the damage if a private key leaks. The safe order:

  1. Publish the new key at a new selector.
  2. Wait for DNS to propagate everywhere.
  3. Switch the platform to sign with the new selector.
  4. Leave the old key published for a while — mail already in transit still references it.
  5. Revoke the old selector with an empty p=, or remove the record.

Many platforms rotate on their own schedule, which is a frequent cause of sudden authentication failures: the provider moved to a new selector and DNS was never updated.

Why DKIM matters more than SPF

SPF checks the connecting IP, so it fails whenever mail is forwarded — the forwarding server is not in your record and never will be. DKIM signs the message, so the signature travels with it and still verifies after a forward.

For DMARC, either mechanism aligning is enough. In practice DKIM is the one that survives real-world mail flow, which makes it the more important of the two to get right.