Keystream Reuse Attack: The Two Time Pad Problem Explained

Keystream Reuse Attack: The Two Time Pad Problem Explained

Written by

in

Stream ciphers are fast and simple. They turn a secret key and a nonce into a long pseudo random keystream, then XOR that keystream with your plaintext to make ciphertext. The whole design rests on one rule: never produce the same keystream twice. A keystream reuse attack is what happens when you break that rule, by reusing a key and nonce together, or by using a one time pad twice. The keystream cancels out and an attacker reads your plaintext without ever touching the key.

How a stream cipher hides data

A stream cipher takes the key K and a nonce N and stretches them into a keystream KS, a sequence of bytes that looks random. Encryption is a single XOR:

C = P XOR KS        # encrypt: ciphertext is plaintext masked by keystream
P = C XOR KS        # decrypt: the same mask removes itself

This works because XOR is its own inverse. Apply the same mask twice and you get back what you started with. The keystream is the mask, and it is only safe as long as each mask is used exactly once. The same idea covers the one time pad, which is a stream cipher whose keystream is truly random and as long as the message. A one time pad is provably unbreakable, but only if the pad is never reused.

Why a keystream reuse attack works

Suppose you encrypt two messages, P1 and P2, with the same key and the same nonce. Both get the identical keystream KS. Now XOR the two ciphertexts together:

C1 = P1 XOR KS
C2 = P2 XOR KS

C1 XOR C2 = (P1 XOR KS) XOR (P2 XOR KS)
          = P1 XOR P2 XOR (KS XOR KS)
          = P1 XOR P2

The keystream appears twice and cancels itself. What the attacker holds is P1 XOR P2, the XOR of the two plaintexts, and no key was needed to get there. This is called the two time pad problem, because a one time pad used twice stops being one time and leaks exactly this.

Reuse the keystream and the ciphertexts stop hiding anything from each other. XOR them and the mask vanishes, leaving the two messages layered on top of each other.

From XOR of plaintexts to the plaintexts themselves

Holding P1 XOR P2 is already a serious leak, and it usually unravels completely. If the attacker knows or can guess any part of one message, that part of the other drops out by XOR. Protocol messages are full of known structure: fixed headers, JSON keys like {"user":, HTTP verbs, predictable field names. Each guessed byte in one message reveals the aligned byte in the other. This technique is called crib dragging: slide a likely word across the combined stream and keep the positions where both results read as sensible text. With a few messages sharing one keystream, the whole set falls.

How the keystream gets reused

Nobody sets out to reuse a keystream. It slips in through the nonce.

  • A fixed or hardcoded nonce. A constant nonce in the source, often a block of zeros left in from a test, means every message under that key shares a keystream.
  • A counter that resets. A device that keeps its nonce counter in memory and restarts at zero after a reboot or crash re emits nonces it already used.
  • A random nonce that is too short. Draw a 64 bit nonce at random under one key and collisions arrive sooner than you expect from the birthday bound. A single collision is one reused keystream.
  • One key copied across machines. Clone a virtual machine that holds the key and nonce state, or share a key across a fleet without coordinating nonces, and two encryptors emit the same nonce under the same key.

This is the same failure shape as AES GCM nonce reuse, which is a stream cipher in counter mode with an authentication tag bolted on. Repeat the nonce and the counter mode keystream repeats exactly as described here, so GCM leaks the XOR of plaintexts and, worse, the authentication key too. A related mistake with block cipher modes is covered in the predictable IV attack, where a guessable initialization vector undermines chained modes.

How to defend against a keystream reuse attack

The defenses are concrete and they stack.

  • Never reuse a nonce under a key. Treat that as a hard invariant. Everything below serves it.
  • Use a counter nonce that never goes backward. A per message counter that strictly increases and never wraps avoids collisions entirely. Persist it so a reboot cannot rewind it.
  • If you use random nonces, make them wide and cap messages per key. Use at least 96 bits from a real CSPRNG and rotate the key well before the birthday bound puts a collision in reach.
  • Rotate keys often. A fresh key resets the whole nonce space and limits the damage from any one slip.
  • Coordinate nonces across machines. Give each encryptor that shares a key a distinct nonce prefix so their ranges cannot overlap, or give each its own key.
  • For a true one time pad, use each pad once and destroy it. The security proof depends on it. A pad reused even once collapses to the two time pad problem above.

For the wider family of these bugs, see our hub on cryptographic oracle attacks, or start with the vulnerability basics category.

The assumption that breaks

A keystream reuse attack is not a weakness in the cipher. The keystream generator can be strong and the XOR is exactly right. The failure is a broken assumption about how the cipher is used, that every nonce under a key is unique. It hides in a constant left in the code, a counter that resets, or a key copied to one machine too many, and the encrypt call looks correct on its own line. Finding it means reasoning about how a nonce is actually produced across the whole system and whether anything can make two of them collide. That is the kind of work an autonomous researcher that tests assumptions is built to do. Read more on our about page.

Frequently asked questions

What is a keystream reuse attack?

It is what happens when a stream cipher produces the same keystream twice, usually because a key and nonce pair was reused. Since encryption is plaintext XORed with the keystream, XORing the two ciphertexts cancels the shared keystream and leaves the XOR of the two plaintexts. The attacker recovers the messages without ever learning the key.

What is the two time pad problem?

A one time pad is unbreakable only if the pad is used exactly once. Use the same pad for two messages and XORing the two ciphertexts removes the pad entirely, leaving the XOR of the two plaintexts. That is the two time pad problem. The same math applies to any stream cipher whose keystream gets reused.

How does an attacker turn the XOR of two plaintexts into the actual messages?

They use crib dragging. Any known or guessable text in one message, such as a fixed header or a JSON key, reveals the aligned bytes of the other by XOR. Sliding likely words across the combined stream and keeping positions where both sides read as sensible text peels the messages apart, especially when several messages share one keystream.

How do I prevent keystream reuse?

Never reuse a nonce under a key. Prefer a counter nonce that strictly increases and never resets, and persist it across reboots. If you use random nonces, make them at least 96 bits from a strong generator and rotate keys well before a collision becomes likely. Coordinate nonces across machines that share a key, and for a true one time pad use each pad only once.


Put an autonomous researcher on your own systems

UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.