Writeup: Caesar Cipher Challenge
Challenge Overview
We are given two files:
encrypt.py: Python script that implements a Caesar cipher (shift cipher) with a secret keyK.output.txt: Contains the resulting ciphertext.
The goal is to recover the original plaintext and submit it as a flag in the format gaslightCTF{...}.
Understanding the Encryption
The encrypt.py script does the following:
from string import ascii_lowercase
def encrypt(pt: str, K: int) -> str:
pt = list(map(lambda x: ascii_lowercase.index(x), list(pt)))
ct = []
for x in pt:
ct.append((x + K) % 26)
ct = list(map(lambda x: ascii_lowercase[x], ct))
ct = class="hljs-string">''.join(ct)
return ct
with open(class="hljs-string">'gaslight-where-the-dream-starts-1/flag_and_key.txt', class="hljs-string">'r') as file:
flag, K = file.readline().split()
K = int(K)
print(encrypt(flag, K))It reads a plaintext flag and an integer key K from a file, then:
- Converts each lowercase letter to its zero‑based index (a=0, b=1, …, z=25).
- Adds
Kto each index modulo 26. - Converts the resulting numbers back to letters.
This is a classic Caesar cipher – each letter is shifted forward by K positions in the alphabet.
The provided ciphertext is:
fdhvduuhdoobolnhgdvkliwriwkuhhSolution Strategy
Since a Caesar cipher has only 26 possible shifts (including 0), we can simply try every possible key and look for the result that produces readable English text. This is a trivial brute‑force attack.
We write a small Python script to:
- Read the ciphertext from
output.txt. - For each shift
Kfrom 0 to 25, decrypt by subtractingK(mod 26). - Optionally, score each candidate based on English letter frequency to automatically detect the correct shift.
- Print the result that looks like English.
Solver Script
# solve.py
def decrypt(cipher, shift):
return class="hljs-string">''.join(chr((ord(c) - 97 - shift) % 26 + 97) for c in cipher)
# Read ciphertext from the file
with open(class="hljs-string">'output.txt', class="hljs-string">'r') as f:
cipher = f.read().strip()
# English letter frequency order (most common first)
freq_order = class="hljs-string">"etaoinshrdlcumwfgypbvkjxqz"
def score(text):
# Give higher score if the text contains more common English letters
return sum((26 - freq_order.index(c)) for c in text if c in freq_order)
best_plain = class="hljs-string">""
best_score = -1
for shift in range(26):
plain = decrypt(cipher, shift)
sc = score(plain)
if sc > best_score:
best_score = sc
best_plain = plain
print(fclass="hljs-string">"Decrypted: {best_plain}")
print(fclass="hljs-string">"gaslightCTF{{{best_plain}}}")Output:
Decrypted: caesarreallylikedashiftofthree
gaslightCTF{caesarreallylikedashiftofthree}Identifying the Correct Shift
Running the script gives us the only English‑looking result:
caesarreallylikedashiftofthreeInserting spaces for readability:
caesar really liked a shift of three
This confirms that the encryption key K is 3 (since “shift of three” is mentioned). The plaintext is a direct description of the cipher and its key.
No other shift produced a coherent English sentence.
Final Flag
Following the given flag format (gaslightCTF{...}), we wrap the plaintext:
gaslightCTF{caesarreallylikedashiftofthree}Takeaways
- Caesar ciphers are extremely weak – only 26 possible keys.
- Always try brute‑force; it’s fast and effective.
- The plaintext may hint at the key itself (“shift of three”).
- Always preserve the exact format expected by the CTF (here
gaslightCTF{}).