Description
A single text file contains an encrypted string; the challenge name hints at a Caesar/ROT-style cipher. Discover the shift that restores the flag.
Download encrypted.txt and read the ciphertext (e.g. xqkwKBN{...}). Get the files from the challenge page on CyLab Security Academy (formerly play.picoctf.org).
Brute-force every shift from 1..25 in Python and look for the row that begins with picoCTF.
cat encrypted.txtpython3 - <<'PY'
from pathlib import Path
cipher = Path('encrypted.txt').read_text().strip()
for shift in range(1, 26):
plain = []
for ch in cipher:
if 'a' <= ch <= 'z':
plain.append(chr((ord(ch) - 97 - shift) % 26 + 97))
elif 'A' <= ch <= 'Z':
plain.append(chr((ord(ch) - 65 - shift) % 26 + 65))
else:
# digits, braces, underscores, punctuation pass through unchanged
plain.append(ch)
line = ''.join(plain)
if line.lower().startswith('picoctf'):
print(f'shift {shift}: {line}')
PYSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Read the cipher from the file
ObservationThere is one file, encrypted.txt. Load it into a variable before trying any shift.Path('encrypted.txt').read_text().strip() avoids hardcoding the ciphertext into the script and survives if the file gets re-issued with a different example.Learn more
Hardcoding the ciphertext into the brute-force script is fine for a one-off, but reading the file is the cleaner habit:
cipher = Path('encrypted.txt').read_text().strip().strip()trims the trailing newline thatread_textincludes, so the loop does not see\nas a non-alphabetic passthrough byte and waste a comparison.Step 2Brute-force all 25 shifts
ObservationThe name says rotation and the ciphertext is all shifted letters like 'xqkwKBN', so this is a Caesar cipher. The shift is the only unknown, and there are just 25 of them, so try them all.Caesar has only 25 non-trivial shifts. Print every candidate and the unique line beginning with picoCTF is the answer.pythonpython3 - <<'PY' from pathlib import Path cipher = Path('encrypted.txt').read_text().strip() for shift in range(1, 26): plain = ''.join( chr((ord(c) - 97 - shift) % 26 + 97) if 'a' <= c <= 'z' else chr((ord(c) - 65 - shift) % 26 + 65) if 'A' <= c <= 'Z' else c for c in cipher ) print(f'{shift:>2}: {plain}') PYExpected output
8: picoCTF{r0tat1o...d140864}What didn't work first
Tried: Use ROT13 (shift 13) directly since that is the most common rotation cipher seen in CTFs.
ROT13 is only correct when the shift really was 13. Applied to a ROT8 ciphertext it just lands on a different wrong plaintext. Nothing in the problem says which offset was used, so loop over all 25.
Tried: Rotate digits along with letters so the entire ASCII printable range shifts uniformly.
Rotating digits breaks the flag: shift '0' by 8 and you get '8', so the body is wrong even at the correct letter shift. A Caesar cipher rotates letters only. Digits, braces, underscores, and punctuation pass through untouched so the picoCTF{...} wrapper survives.
Learn more
The Caesar cipher shifts every letter by a fixed offset, wrapping Z back to A. Encryption with shift s and decryption with shift -s (equivalently, encryption with shift 26 - s) are inverses: if the challenge encoded with +18, you decode with -18 (or +8). The script subtracts the shift, which is the decoding direction.
Why the alphabetic-only branch? Digits, punctuation, braces, and underscores must pass through unchanged so the flag's structure (
picoCTF{...}) survives. If you accidentally rotate digits too, the body of the flag becomes garbage even at the right shift.The keyspace is just 25 candidates. Brute force is not a workaround here, it is the optimal attack: at one millisecond per shift the entire space falls in 25 ms. Anything more sophisticated (frequency analysis, known-plaintext crib of
picoCTFmapping toxqkwKBN) would be overkill and is mainly worth knowing for substitution ciphers with 26! candidate keys instead.For zero scripting, CyberChef has a ROT brute-force operation. The ROT13 Brute Force recipe tries all 25 shifts and prints the lot.
Step 3Submit the flag
ObservationExactly one line of the brute-force output starts with 'picoCTF'. The flag format is rigid, so that line names the correct shift with no ambiguity.The single row that starts with picoCTF is the answer. Copy it without quotes.Learn more
Exactly one shift produces a string starting with
picoCTFbecause flag format is rigid: 7 specific letters in a specific order. Any wrong shift produces gibberish at those positions. That uniqueness is what makes brute-forcing classical ciphers safe in CTFs: there is exactly one plaintext-shaped output, and you cannot accidentally pick the wrong one.
Interactive tools
- ROT / Caesar CipherDecode Caesar-shifted and ROT-encoded text. Drag the shift slider or scan all 26 rotations at once.
- Cipher Identifier & Auto-DecoderPaste any ciphertext and the tool auto-runs every common decoder (base64, hex, Morse, ROT, Atbash, Bacon, binary, decimal, URL) and ranks the results by English-likeness.
Flag
Reveal flag
picoCTF{r0tat1o...d140864}
Any Caesar/ROT decoder works; the correct offset is 8 (the ciphertext was encoded with ROT8).