PicoCTF Mod 26 Writeup — Brute-Force a Caesar Cipher

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

The Caesar cipher has exactly 26 possible shifts (modulo the 26 letters of the alphabet). A Python script that tests all 26 shifts and looks for picoCTF in the result finds the right one in a fraction of a second.

PlatformpicoGym
CategoryCryptography
Points100 pts
DifficultyBeginner
TechniqueCaesar cipher, brute force

Challenge description

The challenge provides a ciphertext and a hint that clearly points the way:

"I made my really long password more secure by adding 26 to each letter... modulo the number of letters."

tmgsgxj{________________________}

The word "modulo" combined with text that looks like letters shuffled in a regular pattern is a strong hint toward a Caesar cipher.

Step 1 — Understand the Caesar cipher

The Caesar cipher is one of the oldest known ciphers: each letter of the plaintext is replaced by the letter located n positions further in the alphabet, where n is the shift (the key). For example, with a shift of 3: A→D, B→E, C→F, etc. Once we reach Z, we wrap back around to A — hence the "modulo 26" in the hint, since the Latin alphabet has 26 letters.

Mathematically, for a letter at position p in the alphabet (A=0, B=1, ..., Z=25) and a shift k:

encryption: c = (p + k) mod 26
decryption: p = (c - k) mod 26

Step 2 — Brute force across the 26 shifts

Caesar's fundamental weakness is that the key space is ridiculously small: there are only 26 possible shifts (0 to 25), one of which does nothing (shift 0) and one of which is the famous ROT13 (shift 13). No need to guess anything: we test all 26 in a loop and see which one produces readable text.

def caesar_decrypt(text, shift):
    result = []
    for ch in text:
        if ch.isalpha():
            base = ord('A') if ch.isupper() else ord('a')
            decoded = chr((ord(ch) - base - shift) % 26 + base)
            result.append(decoded)
        else:
            result.append(ch)  # leave digits, punctuation, spaces untouched
    return ''.join(result)

ciphertext = "tmgsgxj{________________________}"

for shift in range(26):
    print(f"shift {shift:2d} : {caesar_decrypt(ciphertext, shift)}")

Step 3 — Identify the right shift

Running the script gives us 26 lines of output. Most are gibberish, but one jumps right out — the one starting with picoCTF{:

shift  0 : tmgsgxj{________________________}
shift  1 : slfrfwi{...}
...
shift  3 : qjdpduf{...}
shift  4 : picoctf{________________________}
shift  5 : ohbnbse{...}
...
shift 13 : ghtdtjy{...}
...

Shift 4 gives perfectly readable text, with the expected prefix picoctf{ — that's our shift. In practice, you just scan the 26 lines by eye, or script the detection by automatically searching for the substring picoctf{ in each output.

🚩 picoCTF{flag intentionally hidden}

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

Key takeaways

Caesar is the textbook example of a cipher "broken by design": its security relies entirely on the secrecy of the method, not on mathematical robustness. As soon as you know it's Caesar, the key is found instantly.

Resources

Related reading

Crypto 2026-09-14

PicoCTF Substitution0 Writeup — Break a Substitution Cipher with Frequency Analysis

Solving a monoalphabetic substitution cipher with frequency analysis.

Got a question or a different approach?

Discuss this writeup with the community on the CTFdojo Discord.

Join the Discord →