PicoCTF Substitution0 Writeup — Break a Substitution Cipher with Frequency Analysis

Crypto 2026-09-14 · picoGym · By CTFdojo · ⏱ ... · 👁 ... views
𝕏 Share
TL;DR

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...).

PlatformpicoGym
CategoryCryptography
Points150 pts
DifficultyBeginner
TechniqueMonoalphabetic substitution, frequency analysis

Challenge description

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.

Step 1 — The difference from Caesar: monoalphabetic substitution

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".

Step 2 — Frequency analysis

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.

Step 3 — Deduce mappings and iterate

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:

We 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{...}.

Step 4 — Verify with an automatic solver

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.

🚩 picoCTF{flag intentionally hidden}

The flag is deliberately hidden — follow the method, you've earned it. 💪

Key takeaways

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.

Resources

Related reading

Crypto 2026-09-21

PicoCTF Easy1 Writeup — Recover an XOR Key with Crib Dragging

Recovering an XOR key via crib dragging from the flag's known prefix.

Got a question or a different approach?

Discuss this writeup with the community on the CTFdojo Discord.

Join the Discord →