AES-CTR SP 800-38A
Pure stream cipher. Reuse collapses to Cβ β Cβ = Pβ β Pβ β keystream reuse, exactly the XOR reveal. There is no integrity tag here to lose.
IV/nonce reuse Β· NIST SP 800-38A/38D Β· RFC 8439
Encrypt two messages under one key and one nonce across AES-CTR, AES-GCM, ChaCha20-Poly1305, and AES-CBC, then watch each construction break in its own way β recovering plaintext, forging tags, or leaking prefixes against the real primitives.
A nonce β "number used once" β is a per-message value that a cipher mixes in so the same key can safely encrypt many messages. It is not a secret: it travels in the clear next to the ciphertext, and nobody checks it for uniqueness. NIST calls it an initialization vector (IV); the idea is identical.
The ciphers here lean on one rule: never use the same nonce twice under the same key. Break that rule and the math that made them secure turns against you. What makes this lab worth your time is that the four constructions do not fail the same way β and knowing exactly how each one fails is the difference between a shrug and a catastrophe.
No theory required to start. Go to The XOR reveal, reuse a nonce yourself, and drag out a hidden message. Then the rest of the page explains what you just did.
Every cipher operation on this page is real: AES runs in your browser's WebCrypto engine; ChaCha20 and Poly1305 are hand-rolled to the RFC 8439 test vectors so their internals are inspectable. The attacks recover real keys from real ciphertexts and are accepted by the real verifiers. It is still a teaching demo β keys live only in memory for the session, and nothing here is hardened for production use.
This is the headline mechanism. Two messages, one AES-CTR key, one nonce. Because a stream cipher just XORs plaintext with a keystream that depends only on (key, nonce), reusing the nonce makes both messages share the same keystream β and XORing the two ciphertexts cancels it, leaving Pβ β Pβ with no key involved. Drag a guessed piece of one message across it and the other message falls out. This runs against real AES-CTR ciphertext.
Cβ β Cβ = Pβ β Pβ (the keystream is gone)
Recovered bytes of Message 2 under the crib (green = printable text β the crib fits here)
Try dragging the crib to where Message 1 actually reads the (e.g. offset 0). When your guess about Message 1 is right, the true bytes of Message 2 appear β that is crib-dragging, and it needs no key.
The same slip β one nonce, twice, under one key β lands differently on each construction. For each card below, flip the nonce from unique to reused, run it, and read the two separate indicators: what the cipher's own function returned, and the security verdict. They are not the same thing. A tag can verify as VALID and still be a forgery β that reads as an ALARM, never a success.
Two indicators, on purpose. Cryptographic result is the raw return value of the primitive (did it decrypt? did the tag check out?). Security verdict tracks whether the system's guarantee still holds. Colour follows the verdict, not the return value β because a forgery the verifier accepts is the most dangerous outcome of all.
One click breaks (or spares) all four at once, then read the running tally below and the per-card detail underneath. This is the whole thesis in one motion: one mistake, four different failures.
Pure stream cipher. Reuse collapses to Cβ β Cβ = Pβ β Pβ β keystream reuse, exactly the XOR reveal. There is no integrity tag here to lose.
Keystream reuse plus a worse break: two ciphertexts under one nonce let Joux's "forbidden attack" recover the GHASH authentication subkey H, enabling tag forgery. This is authentication-key recovery β not AES key recovery.
Keystream reuse plus recovery of the per-message Poly1305 one-time key (r, s), which is fixed by (key, nonce) β enabling forgery for that nonce. The ChaCha20 key itself is not recovered.
A weaker failure. IV reuse makes CBC deterministic on shared prefixes: two messages with the same leading blocks produce the same leading ciphertext blocks. It leaks prefix equality β not the keystream, not the plaintext.
| Construction | Spec | What nonce reuse breaks | Precise consequence | Enc./AES key recovered? |
|---|---|---|---|---|
| AES-CTR | SP 800-38A | Confidentiality | Keystream reuse β CββCβ = PββPβ; plaintext recovery by crib-dragging. No integrity claim to lose. |
No |
| AES-GCM | SP 800-38D | Confidentiality and integrity | Keystream reuse, plus GHASH authentication subkey H recovery (forbidden attack) β existential forgery under that key. | No β auth key only |
| ChaCha20-Poly1305 | RFC 8439 | Confidentiality and integrity | Keystream reuse, plus Poly1305 one-time key (r, s) recovery β forgery for that nonce. | No β MAC key only |
| AES-CBC | SP 800-38A | Confidentiality (partial) | IV reuse leaks equality of first blocks / message prefixes. Deterministic-encryption pattern leakage, not keystream collapse. | No |
The precision is the point. "Key recovery" is a phrase that hides a huge range. GCM and ChaCha20-Poly1305 leak the authentication key and let you forge; neither leaks the encryption key, so neither hands an attacker the ability to decrypt future messages under a fresh nonce. Overstating that would be a lie the math doesn't support.
Both authenticated modes fall to the same shape of mistake: a secret pad that depends only on (key, nonce) sits inside the tag, and when the nonce repeats, XORing two tags cancels that pad and exposes a clean equation in the one unknown you want. Press each button to watch the cancellation happen β nothing here moves on its own.
The GCM tag is a keyed checksum GHASHH over the ciphertext, with a per-nonce mask EK(Jβ) XORed on top. H = EK(0ΒΉΒ²βΈ) is fixed by the key; the mask is fixed by (key, nonce).
The same mask sits in both tags.
For two equal-length single blocks this collapses to tagβ β tagβ = (Cβ β Cβ)Β·HΒ², solved directly as H = β((tagββtagβ)Β·(CββCβ)β»ΒΉ) over GF(2ΒΉΒ²βΈ). Recover H and the mask, and you can forge a tag for any ciphertext under that nonce. This is exactly what the GCM card runs β the recovered H is checked byte-for-byte against the true EK(0ΒΉΒ²βΈ), and the forged blob is handed to WebCrypto's own verifier.
Poly1305 is a one-time authenticator: for a key (r, s) the tag is (polyr(msg) + s) mod 2ΒΉΒ²βΈ. "One-time" means (r, s) may authenticate exactly one message. In the AEAD, (r, s) is ChaCha20(key, nonce, 0)[0:32] β fixed by (key, nonce).
The same s sits in both tags.
With s gone, a linear (single-block) relation in r remains over the field GF(2ΒΉΒ³β°β5); invert (nββnβ), resolve the small carry from the final mod 2ΒΉΒ²βΈ, and r β then s β fall out. The ChaCha card recovers (r, s) from public tags only, checks them against the true one-time key, and the real Poly1305 verifier accepts the forged tag. The ChaCha20 encryption key never appears in any of this.
Reuse is not exotic. It comes from two ordinary places: random nonces colliding by the birthday bound, and counters that silently rewind.
Pick nonces at random from a 2bits space and a collision becomes likely far sooner than intuition says β around β(2Β·2bits) draws, not 2bits. Slide the message count and watch the probability of at least one repeat climb (computed live from the exact birthday formula).
This is why SP 800-38D limits a single key to 2Β³Β² invocations when 96-bit IVs are generated randomly: past that, the birthday probability of an IV collision β and the total break you just saw β stops being negligible.
A monotonic counter never repeats β until the machine does. Restore a VM snapshot, fork a process, or roll back a container and the counter resumes from an old value, re-emitting nonces already used under the same long-lived key. The counter looks perfect; the environment betrayed it.
The deeper question β how randomness and state themselves collapse β is its own subject. See the Entropy Collapse lab in the Crypto Lab catalog. This lab stops at the nonce and what breaks downstream of it.
Two directions. Guarantee uniqueness β a single encryptor with a persistent counter, or the discipline SP 800-38D demands. Or use a misuse-resistant AEAD that degrades gracefully: AES-GCM-SIV (RFC 8452) derives the IV from the plaintext, so reuse leaks only whether two plaintexts were identical β no keystream collapse, no key recovery.
| Under one reused nonce | AES-GCM (this lab) | AES-GCM-SIV (RFC 8452) |
|---|---|---|
| Confidentiality | Keystream reuse β CββCβ = PββPβ | Leaks only whether two plaintexts are identical |
| Integrity | GHASH subkey H recovered β tag forgery | No key recovery; the tag still binds |
| Cost | Single pass, online | Two passes; must buffer the full plaintext |
Described, not run here. The AES-GCM-SIV column is the RFC 8452 behaviour, not a live computation β this lab deliberately does not implement SIV. To break both side by side against real crypto, see Nonce Guard.