beginnercat/crypto~2 min read

XOR Encoding

Understand XOR's role in CTF crypto — single-byte key recovery, repeating-key XOR, and the crib-drag attack.

// prerequisite reading

XOR in one line

A XOR B flips each bit where B is 1. The crucial property: (A XOR B) XOR B = A. XOR is its own inverse — applying the same key twice returns the plaintext.

plaintext  0110 0001   'a'
key        0101 0101   'U'
ciphertext 0011 0100   '4'

Single-byte XOR

The ciphertext is XOR’d with one repeated byte (0x00–0xFF). Brute-force 256 possibilities and score each result by English letter frequency:

ct = bytes.fromhex("1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736")

def score(s):
    freq = 'etaoin shrdlu'
    return sum(c in freq for c in s.lower().decode('latin-1', errors='ignore'))

best = max(range(256), key=lambda k: score(bytes(b ^ k for b in ct)))
print(bytes(b ^ best for b in ct))

Repeating-key XOR

The key is longer than one byte but shorter than the plaintext, so it repeats.

Attack:

  1. Find the key length — try lengths 2–40; compute the normalised Hamming distance between consecutive chunks. Minimum distance reveals the key length.
  2. Split into slices — collect bytes at positions 0, keylen, 2*keylen … (every byte XOR’d with key[0]), and so on.
  3. Solve each slice as single-byte XOR.

This is the Matasano/Cryptopals Set 1 Challenge 6 — do it once and you’ll always know how.

Crib-dragging (known-plaintext)

If you know or guess a fragment of the plaintext (a crib like flag{ or the ), XOR the crib against the ciphertext at every offset. When the result reads as printable text, you’ve found a hit — the key bytes at that position fall out.

def crib_drag(ct: bytes, crib: bytes):
    for i in range(len(ct) - len(crib)):
        candidate = bytes(a ^ b for a, b in zip(ct[i:], crib))
        if all(0x20 <= c < 0x7f for c in candidate[:len(crib)]):
            print(f"offset {i}: {candidate[:len(crib)]}")

CyberChef shortcut

XOR operation → key type Hex, value 41 (your guessed key byte) → toggle key bytes until the output is readable. Pair with XOR Brute Force for single-byte keys.