Unlike Caesar, each letter is replaced by a different letter according to an arbitrary mapping (not a simple shift). We solve it by comparing the letter frequency of the ciphertext to the known letter frequency in English, and guessing short words (the, and, a...).
| Platform | picoGym |
| Category | Cryptography |
| Points | 150 pts |
| Difficulty | Beginner |
| Technique | Monoalphabetic substitution, frequency analysis |
The challenge provides a fairly long, fully encrypted text, with no hint other than the challenge's name:
"Not all ciphers are too complicated. Sometimes, all you need to do is find the correct letters."
The text contains several hundred characters — that's no accident, as we'll see, the more ciphertext there is, the more reliable frequency analysis becomes.
Here, there's no simple shift: each letter of the alphabet is mapped to another letter according to an arbitrary correspondence (for example A→Q, B→W, C→E...), fixed once and for all for the entire message. This gives 26! (26 factorial) possible combinations — an astronomical number, far too large for naive brute force like on Caesar.
But substitution remains vulnerable to a structural weakness: it preserves the relative frequency of letters. If "E" is the most used letter in English, then in the ciphertext, the most frequent letter very likely corresponds to "E".
We start by counting the occurrence of each letter in the ciphertext with collections.Counter:
from collections import Counter
with open("ciphertext.txt") as f:
text = f.read().lower()
letters = [c for c in text if c.isalpha()]
freq = Counter(letters)
for letter, count in freq.most_common():
pct = 100 * count / len(letters)
print(f"{letter} : {count:4d} occurrences ({pct:.1f}%)")
x : 187 occurrences (12.4%)
q : 143 occurrences ( 9.5%)
z : 121 occurrences ( 8.0%)
j : 98 occurrences ( 6.5%)
...
We compare this ranking to the known letter frequency in English: E (12.7%), T (9.1%), A (8.2%), O (7.5%), I (7.0%), N (6.7%)... First hypothesis: x → E, q → T, z → A, j → O.
Overall frequency gives a starting point, but it's never enough to solve the text in one shot — you have to refine it with structural clues:
A or ITHE or ANDSS, LL, EE) point toward certain common pairsWe progressively substitute the safest letters, re-read the partially decrypted text, and correct wrong hypotheses as recognizable words emerge:
Before: xqz zex ol jax os qjx pxrq ...
After (E,T,A,O known): ..e ..a .. .a. .. e..a...
By iterating several times, the text becomes more and more readable until it reveals complete words, then whole sentences, and finally the expected pattern picoCTF{...}.
To save time or check your work, there are online substitution solvers like quipqiup.com, which use an English dictionary and a search algorithm to automatically suggest the most likely mapping. It's an excellent way to confirm a solution found manually, or to unblock a text too short for reliable frequency analysis.
The flag is deliberately hidden — follow the method, you've earned it. 💪
Monoalphabetic substitution has a huge key space (26!), but it doesn't hide the underlying statistical structure of the language — and that's exactly what frequency analysis exploits.
Recovering an XOR key via crib dragging from the flag's known prefix.
Discuss this writeup with the community on the CTFdojo Discord.
Join the Discord →