Skip to content
All tools
Modern cryptoRuns locallyNo account

XOR crib dragging for many-time pads

Recover both plaintexts when a one-time pad key is reused, by dragging a guessed word along the XOR of two ciphertexts.

Open in ctfpal

A one-time pad is unbreakable exactly once. Reuse the key on a second message and the key cancels: c1 XOR c2 = m1 XOR m2. The key is gone from the equation entirely, leaving two plaintexts XORed together - and English XORed with English is very far from random.

Dragging the crib

Guess a word likely to appear in one message - the , flag, picoCTF{. XOR it against m1 XOR m2 at every offset. At offsets where the guess is wrong you get noise. At the offset where it is right, the output is the corresponding stretch of the other plaintext, and it reads as English. That readable fragment is a new crib, which you drag again. The two messages unzip each other.

xored = bytes(a ^ b for a, b in zip(c1, c2))

for offset in range(len(xored) - len(crib)):
    window = xored[offset:offset + len(crib)]
    guess = bytes(x ^ y for x, y in zip(window, crib))
    if all(32 <= ch < 127 for ch in guess):
        print(offset, guess)      # readable output means the crib fits here

With more than two ciphertexts under the same key the problem gets easier fast - the space trick becomes a majority vote across all pairs and often recovers the key outright.