Description
This XOR encryption scheme reuses parts of the key in a predictable way. Can you exploit it?
Setup
Download the challenge script and output files.
Read the encryption code carefully to understand the XOR structure.
Solution
- Step 1Understand the encryption schemeThe flag is XORed with an unknown key. Then each of 5 fixed strings is XORed with the result some number of times. Because XOR is self-inverting, XORing with the same value an even number of times cancels out -- only strings applied an odd number of times affect the final ciphertext.
Learn more
XOR (exclusive OR) is a bitwise operation with a critical cryptographic property: it is its own inverse.
A XOR B = CimpliesC XOR B = AandA XOR B XOR B = A. Applied an even number of times, any XOR operation cancels perfectly, leaving the original value unchanged. Applied an odd number of times, exactly one application remains effective.This property makes XOR the foundation of stream ciphers and one-time pads, but it also creates predictable weaknesses when keys are reused or when the structure of key application is known. Here, the encryption applies 5 known strings each some number of times -- the unknown is only whether each count is odd or even. This collapses the key space from an exponentially large set to exactly 2^5 = 32 possibilities.
Understanding the algebraic structure of an encryption scheme -- which operations cancel, which are redundant, what the minimal effective key space is -- is the essence of cryptanalysis. Rather than attacking the algorithm itself, this challenge attacks the implementation's key schedule: the specific pattern of how keys are applied creates exploitable structure.
- Step 2Enumerate all 32 combinationsThere are 5 fixed strings, and each was either applied an odd or even number of times -- giving 2^5 = 32 possible combinations. Brute-force all combinations, XOR them against the ciphertext, and check which result starts with the flag prefix picoCTF{.python3 -c " from itertools import product # Load ciphertext and the 5 fixed strings ciphertext = bytes.fromhex(open('enc_flag').read().strip()) strings = [b'...', b'...', b'...', b'...', b'...'] for bits in product([0,1], repeat=5): result = bytearray(ciphertext) for use, s in zip(bits, strings): if use: for i in range(len(result)): result[i] ^= s[i % len(s)] if result[:7] == b'picoCTF': print(result.decode()) "
Learn more
itertools.product([0,1], repeat=5) generates all 32 combinations of five binary values: (0,0,0,0,0), (0,0,0,0,1), ..., (1,1,1,1,1). This is equivalent to counting from 0 to 31 in binary. Each tuple represents a "which strings were applied an odd number of times" configuration -- 1 means apply this string, 0 means skip it (even applications cancel).
Known-plaintext validation uses the known flag prefix
picoCTF{as a filter. After XORing the ciphertext with a candidate combination of strings, the result is valid only if it starts with the expected bytes. This check eliminates all incorrect combinations immediately -- with 32 possibilities and 7 bytes of known prefix, only the correct combination (and possibly no false positives) passes.This brute-force is feasible because the key space is tiny (32 options) and each XOR operation is O(n) in the flag length. The total work is 32 * 5 * flag_length operations -- trivially fast even in pure Python. For key spaces up to about 2^40, brute force with known plaintext checking is often the right approach in cryptographic CTF challenges.
The repeating key XOR (
s[i % len(s)]) handles fixed strings shorter than the ciphertext by cycling them -- the same technique used in the Vigenère cipher and many stream ciphers. If the key period were unknown, frequency analysis or index-of-coincidence methods could find it. Here, the strings are known, so no analysis is needed.
Flag
picoCTF{...}
XOR with the same value twice returns the original -- strings applied an even number of times cancel completely, leaving only 32 effective combinations to test exhaustively.