DKIM Replay: Why a Valid Signature Doesn't Prove the Sender

A DKIM “pass” feels like proof that the sender is who they claim to be. It is not - and a DKIM replay attack is the clearest way to see the gap. DKIM signs the content of a message, not the act of sending it, so one validly signed message can be captured and re-sent to millions of new recipients with the signature still passing. This is a known limitation, and a successor - DKIM2 - is now being actively discussed at the IETF to close exactly this gap. This post explains what a DKIM signature does and does not prove, walks through the four consequences of that design, gives you a safe lab to watch a replay happen with your own domain, and shows where DKIM2 fits in.
What a DKIM pass actually proves
When DKIM verifies, it tells you one specific thing: the signed parts of this message were signed by a key published under this domain, and have not changed since. That is genuinely useful - it means the body and the signed headers are intact and vouched for by the domain’s key.
But look at what is not covered by the signature:
- Who the message is for. The SMTP envelope recipient (
RCPT TO) is never signed. - Which server sent it. The connecting IP is not part of the signature.
- How many times it has been sent. Nothing counts deliveries.
- When, in practice. DKIM has an optional expiry (
x=), but many signers never set a short one.
In other words, DKIM authenticates the message, not the transaction. A signature is a portable stamp that travels with the bytes and re-validates anywhere, for anyone, until the key rotates. That single fact is the root of the four problems below.
A correctly signed message can be captured and replayed to millions of recipients as if they had been BCC'd. The signature stays valid, so spammers ride a trusted domain's reputation. DKIM has no built-in defence.
Plain forwarding leaves DKIM intact - but the moment a mailing list or gateway edits the subject or adds a footer, the signed hash no longer matches and DKIM fails. A list can then fail DMARC on both checks at once.
When an origin is forged and the final hop can't deliver, the bounce (DSN) is sent to an innocent third party whose address was spoofed.
Authenticated Received Chain (RFC 8617) was bolted on to carry authentication across hops, but it can't tell whether a receiver will actually recognise or trust the chain.
1. Replay: the same signature, a million times
This is the sharpest consequence. Because the recipient is not in the signature, an attacker can:
- Get one validly signed message from a high-reputation domain - usually by signing up for a free account at a large provider that DKIM-signs outbound as its own domain, then sending the message to their own inbox (a single, innocuous-looking send that passes outbound checks).
- Capture the raw signed message,
DKIM-Signatureheader and all. - Re-inject that exact message - unchanged - to millions of real victims from their own infrastructure, setting only the envelope
RCPT TOper victim.
Every receiver verifies the signature against the reputable domain, it passes, DMARC aligns, and the message inherits a reputation the real brand earned. The brand never sent it. It was replayed.
The uncomfortable summary
A DKIM pass tells you a message was signed by a domain at some point. It does not tell you that the domain sent this copy, from this server, to you.
2. Modifying forwarders break the signature
There is a well-known rule of thumb: “SPF breaks on forwarding, DKIM survives.” For a plain forward that leaves the message untouched, that is exactly right - DKIM passes because nothing changed. The trouble is the intermediaries that change the message. The moment a mailing list adds a [list-name] subject tag or an unsubscribe footer, a signed header or the body changes, the hash no longer matches, and DKIM fails.
| Scenario | SPF | DKIM | DMARC result |
|---|---|---|---|
| Plain forward (unchanged) | fail (new IP) | pass | passes via DKIM |
| Mailing list (subject tag + footer) | fail | fail (body changed) | fails - both broken |
That second row is the infamous “mailing lists break DMARC” problem: both mechanisms fail at once, so there is nothing left to align. It is exactly what ARC was bolted on to rescue.
3. Backscatter: bounces to forged third parties
When a message’s origin has been forged and the final hop cannot deliver it, the Delivery Status Notification (the bounce) is sent to the address in the envelope - which may be an innocent third party whose address was spoofed. They receive a bounce for mail they never sent.
4. ARC is a patch, not a fix
Authenticated Received Chain (RFC 8617) was added to carry authentication across forwarders and lists by having each hop attest to what it saw. It helps, but it is additive complexity on top of DKIM, and it cannot tell a sender whether a given receiver will actually recognise or trust the chain. It manages the symptom rather than closing the underlying gap.
See it yourself: a safe replay lab
The fastest way to internalise this is to watch a signature survive re-delivery to a recipient it was never bound to. You can do it in a few minutes with your own key and mailboxes.
Ground rules - read first
Use only a domain, DKIM keys, and mailboxes you own. Send single test messages to your own test inboxes. Never replay another party's signed mail, and never send to recipients who have not consented. The point is to observe dkim=pass surviving a re-send, not to send volume. That line is the difference between a lab and abuse.
1. Create a key and publish the public half in DNS for a test domain you control:
openssl genrsa -out lab.private 2048
openssl rsa -in lab.private -pubout -out lab.public
# publish: selector._domainkey.yourlab.test TXT "v=DKIM1; k=rsa; p=<base64 of lab.public>"
2. Sign a test message (msg.eml with a From, To, Subject and a line of body):
dkimsign selector yourlab.test lab.private < msg.eml > signed.eml
dkimverify < signed.eml
# signature ok
In the wild
Here you write and sign a harmless msg.eml with your own key. In a real attack this file is the abuser's payload - the spam or phishing content they got a reputable provider to sign for them, by emailing it to an inbox they control (for example, sending it from a throwaway Gmail account to themselves so Google signs it with gmail.com). They never hold the key; the provider signs their chosen content, and they simply capture the signed result. The lab uses your own key so you can watch the exact same mechanism without borrowing anyone's reputation.
3. Replay it. Re-send the identical signed.eml to a different mailbox, from a different envelope sender. Only the envelope changes; the message bytes do not. swaks lets you set the envelope independently of the headers:
swaks --server <your-receiver-mx> \
--from [email protected] \
--to [email protected] \
--data signed.eml
swaks (the “Swiss Army Knife for SMTP”) is a small command-line tool for sending test email, and it is the right tool here because it lets you set the SMTP envelope (--from and --to) independently of the message headers and send a raw message with --data. That independence is the whole point of the demonstration: on a replay the envelope recipient is all that changes, while the signed message stays byte-for-byte identical. It is a single Perl script with no service to run, and on Ubuntu or Debian it installs in one command:
sudo apt install swaks
4. Check the delivered message’s headers (in Gmail: Show original). You will see DKIM: PASS for yourlab.test even though it went to a recipient that was not the original To, from a different envelope sender, down a different path. The signature travelled with the content and re-validated for a recipient it was never bound to. That is a replay, in miniature.
5. Prove the two boundaries. Re-sign with a short expiry (x=) and show the replay fails once expired - the mitigation DKIM2 makes mandatory. Then change a single byte of the body and watch DKIM fail - confirming the signature is bound to the content but never to the recipient.
A real case: fake PayPal and Apple invoices
You do not have to imagine this - it is an active scam. Attackers never touch PayPal’s or Apple’s servers. Instead they abuse a user-controlled field in a legitimate workflow: the “seller name” on a PayPal invoice or dispute, or the account name on an Apple App Store purchase. They type the scam straight into that field - a fake charge and a phone number, for example “To cancel, call +1 (803) ***-****” - which makes the vendor email a real, itemised invoice or receipt (with that text inside it) to an address the attacker controls.
Because PayPal or Apple generated and DKIM-signed that message, it is genuinely authenticated. The attacker then forwards the untouched email to their targets. Here is the subtle part: they cannot change the visible To: header, because the vendor signed it - editing it would break DKIM. So they leave To: set to their own address and redirect delivery through the SMTP envelope instead (the RCPT TO, which DKIM never signs). A plain forward changes none of the signed headers or body, so DKIM stays valid; SPF fails from the attacker’s server, but DMARC passes on the aligned DKIM alone. The victim receives what looks like a real PayPal or Apple notice - correct branding, valid signature, no spam warning - and calls the number, where a fake “support agent” walks them into handing over money or account access.
How to spot one
A genuine PayPal or Apple invoice is addressed to you. Because the attacker cannot touch the signed To: header, a replayed copy still carries the address the message was originally sent to - often a stranger's, or simply not yours. If a vendor "invoice" reaches your inbox but the To: line is not your address, treat it as a replay and never call a number inside it.
The same trick has hit DocuSign, HelloSign, and even Google’s own [email protected] in 2025. The common thread is the whole point of this post: the signature is real and the vendor really did send it - it has just been re-aimed at victims, with the attacker’s payload smuggled in through a form field.
So what actually stops replay today?
Since DKIM cannot stop it, receivers do - with heuristics. Large mailbox providers watch for the same signature (b=) arriving at scale from many IPs and rate-limit or flag it. It works, mostly, but it is a receiver-side arms race rather than a property of the protocol.
The proposed fix is a protocol one. DKIM2, now an active IETF DKIM working-group draft (draft-ietf-dkim-dkim2-spec), binds each signature to the envelope sender and its intended recipient set, and has verifiers reject signatures more than 14 days old - so a replayed copy no longer validates for a recipient it was not signed for. It is early and years from deployment, but it targets exactly the gap this post is about. (You can read the case for it in the DKIM2 motivation draft.)
What a sender can do today
You cannot stop a receiver from accepting a replay, but you can put a clock on your own signatures. DKIM has an optional expiry tag, x=, that sets a time after which the signature should no longer be honoured. Set it, and a captured signature has a short shelf life instead of an open-ended one - the same idea DKIM2 makes mandatory.
- Add a signature expiry of about a week. Most signers (Amavis, OpenDKIM and the like) have a one-line “signature TTL” setting. Around 7 days is a sensible, conservative choice - it sits well inside the 14-day ceiling the DKIM2 draft sets, after which verifiers should reject a signature.
- Do not set it too short. A window of hours risks failing your own mail when delivery is delayed by greylisting, queued retries or slow relays. A week is safe for weekly digests and transactional sends.
- Know the limit. Receivers may honour
x=rather than must, but the major mailbox providers do consider it, so it is a real, cheap mitigation - just not an absolute one.
One common mix-up: the x= expiry is not the same as the DNS TTL on your DKIM key record. The TTL only controls how long resolvers cache your public key - it does nothing to a signature’s validity, so a short TTL does not stop replay. Signature lifetime lives in x= (per message). The only DNS-side lever is coarser: rotate DKIM keys periodically and revoke an old selector (delete it, or publish an empty p=), which invalidates every signature ever made with it. A lower TTL just makes that revocation propagate faster.
Together, x= bounds each signature and key rotation bounds each key - and that combination is the sender’s whole toolkit until DKIM2 ships. (It is on our own list: our tools sign through a single relay, and we are adding a one-week signature expiry there.)
What this means for you
- A DKIM pass is necessary, not sufficient. Treat it as “the content is vouched for,” not “the real sender sent me this.”
- Get SPF, DKIM and DMARC right anyway. They remain the baseline, and a clean domain is well-placed for whatever ships next. Our free DMARC monitor shows you exactly who is signing as your domain.
- If you send through ESPs, understand replay. It is a present-day weakness, and knowing it changes how you read a “pass”.
Not sure who is really sending as your domain?
Our free DMARC monitor turns your aggregate reports into a plain-English weekly summary: every source sending as you, and whether each one is signing correctly.
Get free DMARC monitoring Talk to a deliverability expertFrequently asked questions
Is DKIM broken?
No. DKIM does exactly what it was designed to do - prove that a message’s signed content came from a domain’s key and was not altered. The issue is what people assume it proves. It does not prove that the domain sent this particular copy to this particular recipient, which is why replay is possible.
What is a DKIM replay attack?
A DKIM replay attack takes one validly signed message from a reputable domain and re-sends the exact bytes, unchanged, to many new recipients. Because the recipient and sending IP are not part of the signature, DKIM still passes, so the spam inherits the reputable domain’s sending reputation.
Does SPF or DMARC stop DKIM replay?
Not on their own. A replayed message keeps a valid, aligned DKIM signature, so DMARC passes via DKIM even though SPF fails for the attacker’s IP. Stopping replay today relies on receiver-side heuristics (detecting the same signature at scale), not on SPF, DKIM or DMARC themselves.
Why does the attacker get the message signed first, instead of just sending the spam directly?
Because sending directly cannot scale and gets filtered. A provider that would sign your mail - a trusted ESP, or a free Gmail account - scans outbound mail for spam and phishing, caps how many recipients you can reach, and bans accounts that abuse it, so a direct blast is stopped on both content and volume. Replay gets around this by splitting the signing from the sending. Step 1: the attacker drafts the payload and sends a single, ordinary-looking copy to an inbox they control through a trusted ESP that DKIM-signs its outbound mail; that one low-volume send slips past the ESP’s abuse checks and is signed with the ESP’s sending domain. Step 2: the attacker captures the raw signed message and, from their own infrastructure, re-sends the identical bytes in bulk - same From, same DKIM signature (for example From: [email protected] with a valid d=trusted-esp.com signature) - so every copy still passes DKIM and DMARC, now with no sending limits and no content filtering. Replay borrows a trusted sender’s reputation while escaping its sending controls, possible only because a DKIM signature is detached from delivery.
Does a DMARC reject policy (p=reject) stop DKIM replay?
No. A replayed message carries a genuinely valid, aligned DKIM signature, so it passes DMARC - and p=reject only rejects mail that fails DMARC. The replay is authenticated mail re-sent to unintended recipients, not a forged From address, so there is nothing for reject to act on. DMARC and p=reject are an anti-spoofing tool: they stop a forged sender with no valid aligned authentication, but they cannot see a replay because the signature is real. That is exactly the gap DKIM2 closes - by binding the signature to the intended recipients, a replayed copy becomes invalid, which finally makes DMARC fail and lets p=reject reject it.
What is DKIM2 and will it fix this?
DKIM2 is a proposed successor to DKIM, currently an IETF working-group draft. It binds each signature to the envelope sender and its intended recipients, and has verifiers reject signatures more than 14 days old, which would make a replayed copy invalid for any recipient it was not signed for. It is early-stage and not deployed, so it changes nothing today.