Backend
PicoCTF Easy1 Writeup — Recover an XOR Key with Crib Dragging
CTFDojo Dev.to (EN Zone)
26 views
The flag is XOR-encrypted with a short repeating key. Since we know every picoCTF flag starts with picoCTF{, we XOR that known text ("crib") with the beginning of the ciphertext to recover the first bytes of the key, then complete the key by finding the repeating pattern.
Platform: picoGym
Category: Cryptography
Points: 200 pts
Difficulty: Intermediate
Technique: XOR, crib dragging
Challenge description
The challenge provides a hex-encoded ciphertext file, along with the Python script used to encrypt it:
KEY = ??? # unknown, short key
def encrypt(data, key):
return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
with open("flag.txt", "rb") as f:
flag = f.read()
ciphertext = encrypt(flag, KEY)
print(ciphertext.hex())
$ cat ciphertext.txt
3d262c3c061909340c1a1210100c1a1210100c1a1210100c1a1210102e
The KEY is unknown, but the encryption scheme is crystal clear: repeated byte-by-byte XOR, cycling through the key (i % len(key)). It's a classic construction — and classically vulnerable as soon as you know a fragment of the plaintext.
Step 1 — Understand XOR's self-inverse property
The XOR operator has a mathematical property that's very handy for the attacker: it's its own inverse. If ciphertext = plaintext XOR key, then:
plaintext XOR ciphertext = plaintext XOR (plaintext XOR key) = (plaintext XOR plaintext) XOR key = 0 XOR key = key
In other words: as soon as you know both a chunk of plaintext and its corresponding ciphertext, you can recover the key directly, without having to guess it.
Step 2 — Crib dragging with the known prefix
Every picoCTF flag follows the same format: it starts with picoCTF{. That's our "crib" (known plaintext). We XOR this crib with the first bytes of the ciphertext to deduce the first bytes of the key:
ciphertext_hex = "3d262c3c061909340c1a1210100c1a1210100c1a1210100c1a1210102e"
ciphertext = bytes.fromhex(ciphertext_hex)
crib = b"picoCTF{"
key_fragment = bytes(c ^ k for c, k in zip(ciphertext, crib))
print(key_fragment)
b'MOOSEMOO'
We get a key fragment. A pattern jumps out right away: the first 5 bytes MOOSE repeat at the start of the next 3 bytes (MOO) — a clear sign that the real key is 5 bytes long and we simply went past one period with an 8-character crib.
Step 3 — Determine the key length
Looking at key_fragment = MOOSEMOO, the most natural hypothesis is a 5-byte key: MOOSE, which indeed starts over at the 6th position (the following M, since the 8-character crib overruns one period of the 5-byte key).
key = b"MOOSE"
print(len(key)) # 5
This is consistent: a short 5-byte key cycling over the whole message, exactly as expected from the encryption script given in the challenge.
Step 4 — Decrypt the full message
With the complete key in hand, we decrypt the whole file:
from itertools import cycle
ciphertext = bytes.fromhex(ciphertext_hex)
key = b"MOOSE"
plaintext = bytes(c ^ k for c, k in zip(ciphertext, cycle(key)))
print(plaintext.decode())
picoCTF{____________________}
The picoCTF{ prefix confirms the recovered key is correct, and the rest of the message decrypts cleanly through to the closing brace.
🚩 picoCTF{ flag intentionally hidden }
The flag is deliberately hidden — follow the method, you've earned it. 💪
Key takeaways
This challenge illustrates a known-plaintext attack on short repeating-key XOR: knowing just a fragment of the plaintext is enough to recover the entire key, and therefore the entire message.
XOR with a short repeating key is vulnerable as soon as part of the plaintext is known — the fixed format of CTF flags (picoCTF{) is often that known fragment
Never reuse a short XOR key across multiple blocks or messages: it's the same structural weakness as the Vigenère cipher
For real confidentiality needs, prefer a modern authenticated cipher like AES-GCM or ChaCha20-Poly1305, never repeated XOR
Originally published on CTFdojo — join the CTFdojo Discord to discuss writeups and get notified about new ones.
Read original: https://dev.to/ctfdojo/picoctf-easy1-writeup-recover-an-xor-key-with-crib-dragging-3p8o
← Previous
I Finally Built My Developer Portfolio
Next →
Show DEV: Boniatillo - La red social cubana que permite monetizar tu contenido (construida en PHP)
Related
The benchmark everyone cited for "fastest backend" was archived in March, and the reason is the interesting part
Backend
56
Dev.to (EN Zone)
Finding Exoplanets in Noisy Data with Machine Learning
Backend
46
DEV Community
How to automatically find the batch size when using Accelerate with FSDP2? [D]
Backend
43
Reddit r/MachineLearning
Cross-Chain Bridge Risk Assessment: Aave V3
Backend
36
DEV Community
Comments0
No comments yet — be the first