XOR Cryptography
cryptobeginner
Exploit XOR cipher weaknesses: single-byte key, multi-byte repeating key, and known plaintext attacks.
cryptoxorstream-cipherkey-recoveryknown-plaintext
XOR Cryptography
XOR is the foundation of many CTF crypto challenges - from trivial single-byte keys to more complex multi-byte repeating key ciphers (essentially a Vigenère cipher in binary). Understanding XOR properties is essential for any CTF player.
XOR Properties
If , then and (known plaintext reveals the key).
Attack: Single-Byte XOR Brute Force
The key is a single byte (256 possibilities). Try all 256 and score by English letter frequency:
from collections import Counter
def score(text):
freq = "etaoinshrdlu "
return sum(c in freq.lower() for c in text.lower().decode(errors='ignore'))
def single_byte_xor_crack(ciphertext):
best_score = 0
best_key = 0
best_plain = b""
for key in range(256):
plain = bytes([b ^ key for b in ciphertext])
s = score(plain)
if s > best_score:
best_score = s
best_key = key
best_plain = plain
return best_key, best_plain
ct = bytes.fromhex("1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736")
key, plain = single_byte_xor_crack(ct)
print(f"Key: {hex(key)} → {plain}")Attack: Multi-Byte Repeating Key (Vigenère in Binary)
Step 1: Find Key Length (Kasiski / Index of Coincidence)
def hamming_distance(a, b):
return bin(int.from_bytes(a, 'big') ^ int.from_bytes(b, 'big')).count('1')
def find_key_length(ciphertext, max_keylen=40):
distances = {}
for keylen in range(2, max_keylen + 1):
# Average normalized Hamming distance between consecutive blocks
chunks = [ciphertext[i:i+keylen] for i in range(0, len(ciphertext) - keylen, keylen)]
dists = [hamming_distance(chunks[i], chunks[i+1]) / keylen
for i in range(min(4, len(chunks) - 1))]
distances[keylen] = sum(dists) / len(dists)
# Smallest distance = most likely key length
return sorted(distances, key=distances.get)[:5]
keylens = find_key_length(ciphertext)
print("Likely key lengths:", keylens)Step 2: Crack Each Byte of the Key
def crack_repeating_xor(ciphertext, keylen):
key = b""
for i in range(keylen):
# Every keylen-th byte was encrypted with the same key byte
block = bytes(ciphertext[j] for j in range(i, len(ciphertext), keylen))
k, _ = single_byte_xor_crack(block)
key += bytes([k])
return key
key = crack_repeating_xor(ciphertext, keylens[0])
plaintext = bytes([c ^ key[i % len(key)] for i, c in enumerate(ciphertext)])
print(f"Key: {key}\nPlaintext: {plaintext}")Attack: Known Plaintext
If you know (or can guess) any portion of the plaintext:
known_plain = b"flag{"
ciphertext = bytes.fromhex("...")
# Recover the corresponding key bytes
key_fragment = bytes([p ^ c for p, c in zip(known_plain, ciphertext)])
print(f"Key starts with: {key_fragment}")
# If key is repeating, infer full key from fragmentIn CTFs, the flag format itself (flag{) is 5 known bytes - use it!
Two-Time Pad: Reused Key
If the same XOR key is used to encrypt two messages:
Knowing either plaintext recovers the other:
c1 = bytes.fromhex("...")
c2 = bytes.fromhex("...")
xored = bytes([a ^ b for a, b in zip(c1, c2)]) # P1 xor P2 (bitwise)
# If P1 has known characters (spaces, lowercase letters)
# You can recover P2 statistically by crib-draggingCrib Dragging
def crib_drag(ct1, ct2, crib):
xored = bytes([a ^ b for a, b in zip(ct1, ct2)])
for i in range(len(xored) - len(crib)):
result = bytes([xored[i+j] ^ crib[j] for j in range(len(crib))])
print(f"Offset {i:3d}: {result}")
# Try common cribs
crib_drag(c1, c2, b"the ")
crib_drag(c1, c2, b"flag{")
crib_drag(c1, c2, b" and ")CyberChef XOR
For quick XOR in the browser:
- CyberChef → XOR operation
- Input: your ciphertext (hex)
- Key: your key (hex or UTF-8)
- Scheme: Standard (or cycling for repeating key)
Checklist
- Single byte? → Brute force all 256 keys, score by English frequency
- Multi-byte? → Find key length via Hamming distance, then break each byte
- Know part of plaintext? → Known-plaintext recovery:
- Two ciphertexts, same key? → XOR them together, crib-drag
- Key starts repeating visually? → Determine period and attack each position
Last updated on