Skip to main content

interencdec picoCTF 2024 Solution

enc_flag is Base64 inside Base64 inside a Caesar shift. Decode each layer in order to uncover the flag.

Published: April 3, 2024Updated: August 25, 2026

Description

Can you get the real meaning from this file. Download the file here.

Local decode

Download the enc_flag file from the challenge artifacts.

Work locally; no remote service is needed once you have the blob.

bash
wget https://artifacts.picoctf.net/c_titan/3/enc_flag && \
cat enc_flag

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
The CTF Encodings guide walks through how to recognize Base64, ROT/Caesar, hex, and the other layered transforms this challenge stacks together.
  1. Step 1Read the initial Base64
    Observation
    enc_flag is a single long run of letters and digits ending in ==, and its length is a multiple of 4. That is the Base64 fingerprint, so decode it first.
    cat enc_flag prints a long Base64 string ending in ==. Decode it once to reveal a Python byte literal (b'...').
    bash
    base64 -d enc_flag
    What didn't work first

    Tried: Running 'base64 enc_flag' (without -d) to decode the file

    Without -d, the base64 command encodes the file a second time rather than decoding it, producing a longer Base64 blob. The -d flag is required to switch from encode mode to decode mode; omitting it is a very common first-attempt mistake.

    Tried: Opening enc_flag in a hex editor to look for embedded data before trying Base64

    The file is printable ASCII arranged as valid Base64, with no binary header or file signature for a hex editor to find. Recognize the alphabet and the padding, and decode.

    Learn more

    Base64 is an encoding scheme, not encryption. It converts arbitrary binary data into a safe printable-ASCII string using 64 characters (A-Z, a-z, 0-9, +, /). Every 3 bytes of input become 4 Base64 characters, which is why Base64-encoded data is always about 33% larger than the original.

    The == at the end is padding. Base64 works in 3-byte groups; if the input isn't a multiple of 3 bytes, one or two = characters are appended as placeholders so the length is always a multiple of 4.

    Base64 is everywhere: email attachments (MIME), embedding images in CSS (data:image/png;base64,...), JWTs (the header and payload are Base64URL-encoded), and passing binary data through systems that only handle text. Seeing a string that ends in == or is unusually long and uses only alphanumeric characters is a strong hint to try Base64 decoding it.

  2. Step 2Strip quotes and decode again
    Observation
    The first decode gives a Python bytes literal wrapping another long alphanumeric string, so the payload was encoded twice. Strip the b'...' wrapper before decoding again.
    Remove the leading b' and trailing ' (cut -d "'" -f2 works on the typical bytecode print format), then Base64-decode the inner string to obtain a Caesar-shifted message.
    bash
    base64 -d enc_flag | cut -d "'" -f2 | base64 -d
    What didn't work first

    Tried: Piping the first base64 decode directly into base64 -d a second time without stripping the b'...' wrapper

    The second base64 -d call receives 'b\'<inner>\'' as input. The leading 'b\'' characters are not valid Base64 and cause an 'invalid input' error. The Python bytes-literal wrapper must be stripped first before the inner Base64 can be decoded.

    Tried: Using tr to strip the wrapper instead of cut, such as tr -d "b'"

    tr -d deletes those characters everywhere, including any 'b' inside the Base64 payload itself, corrupting the data before you decode it. Splitting on the quote character is safe, because the Base64 alphabet never contains one.

    Learn more

    The b'...' wrapper is Python's syntax for a bytes literal. When Python prints a bytes object it adds this prefix so you can tell it apart from a regular string. It's not part of the data, just how Python represents it in text form.

    cut -d "'" -f2 splits on the single-quote character and takes the second field, which works cleanly when the inner Base64 contains no quotes (it never will, since the Base64 alphabet is A-Za-z0-9+/=) and there is no whitespace before the opening b'. If you ever see surrounding whitespace or a different quoting style (b"..."), reach for sed -E "s/^b['\\"]//; s/['\\"]$//" instead.

    The key insight: encoding is not encryption. No secret key is involved; anyone who recognizes the encoding can reverse it. Real encryption (AES, RSA) requires a key you don't have. Encoding is purely a format transformation.

  3. Step 3Apply ROT13 / Caesar
    Observation
    The second decode gives wpjvJAM{...}, the same shape and length as picoCTF{...} with every letter moved by a fixed amount. That is a Caesar cipher, and there are only 25 shifts to try.
    The decoded text is 'wpjvJAM{jhlzhy_k3jy9wa3k_i204hkj6}' - a Caesar-shifted picoCTF{...}. Brute all rotations (CyberChef's ROT13 operation with 'Brute force all rotations' is the easiest path) or pipe through bsdgames caesar, then take the candidate starting with 'pico'. The shift that works here is 7 forward, so you undo it by rotating 19.
    bash
    base64 -d enc_flag | cut -d "'" -f2 | base64 -d | caesar 19

    Expected output

    picoCTF{caesar_d3cr9pt3d_b20...}

    caesar from bsdgames applies English letter-frequency analysis and outputs a single best-guess decryption; if that guess is wrong you can pass an explicit shift number (e.g. caesar 19) to try a specific rotation. CyberChef's ROT13 recipe with "Brute force all rotations" checked is the friendliest option.

    What didn't work first

    Tried: Running 'rot13' or 'tr A-Za-z N-ZA-Mn-za-m' and expecting it to produce the flag immediately

    ROT13 is a fixed shift of 13, and this challenge does not use 13: the text is shifted 7, so ROT13 returns nonsense. Try all 25 shifts and take the one starting with picoCTF{.

    Tried: Trying to decrypt the shifted text with openssl or gpg, assuming the Caesar layer is actual encryption

    A Caesar cipher is not encryption in any sense openssl or gpg understands: no key, no IV, no cipher mode. Feed one to those tools and you get an error or garbage. Use a substitution brute-forcer, or shift the alphabet by hand.

    Learn more

    A Caesar cipher shifts each letter by a fixed number of positions in the alphabet. ROT13 is a Caesar cipher with a shift of 13; it's its own inverse (applying it twice gives you back the original), which made it popular for hiding spoilers in early internet forums.

    With only 25 possible shifts, the right one is whichever produces the recognizable picoCTF{ prefix. That single landmark, plus the known flag format, is the only signal you need; full English frequency analysis is overkill on a 40-character flag and is the standard tool for the Vigenère-style longer ciphertexts where you don't already know the plaintext skeleton.

    Historically, Julius Caesar reportedly used a shift of 3. The cipher was reasonably secure in an era when most people were illiterate, but provides zero real security today. It's the ancestor of the Vigenère cipher (a repeating-key Caesar), which itself was cracked in the 1800s using the Kasiski test.

Interactive tools
  • Base64 & Base32 DecoderDecode Base64 and Base32 strings with auto-detection. Multi-layer mode unwraps nested encodings automatically.
  • 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.
No-terminal path (recommended)

If you don't want to fight quoting in the shell, the fastest solve is entirely in the browser. Open the Base64 Decoder, paste the contents of enc_flag, and decode. Strip the b' wrapper from the result, paste that back in, and decode again. Drop the final string into the ROT / Caesar Cipher tool and click Try all 26 shifts; the line starting with picoCTF{ is the flag.

Flag

Reveal flag

picoCTF{caesar_d3cr9pt3d_b20...}

Two Base64 layers plus a Caesar shift are all that stand between you and the flag.

Key takeaway

Base64 and Caesar shifts protect nothing: no secret key, and the transformations are public and reversible. Stacking them adds steps, not security, because each layer peels off on its own once you recognize it. Learning those fingerprints, the Base64 alphabet and padding, the fixed shift pattern, carries straight into reading JWT payloads, obfuscated malware strings, and encoded config files.

Related reading

Tools used in this challenge

Where to go next