Skip to main content

Safe Opener picoCTF 2022 Solution

Reverse engineer a Java program to extract the password needed to unlock the flag.

Published: July 20, 2023Updated: August 25, 2026

Description

The SafeOpener Java program stores an encoded password. Decode it, then wrap the plaintext inside picoCTF{...} to submit.

Open the Java source-openSafe() defines the Base64-encoded password (encodedkey).

Extract the string, decode it, and either run the program with that password or directly wrap it with picoCTF{...}.

bash
grep encodedkey SafeOpener.java
bash
grep -o '"[A-Za-z0-9+/=]\{16,\}"' SafeOpener.java
bash
grep -o '"[A-Za-z0-9+/=]\{16,\}"' SafeOpener.java | tr -d '"'
bash
grep -o '"[A-Za-z0-9+/=]\{16,\}"' SafeOpener.java | tr -d '"' | base64 -d
bash
javac SafeOpener.java && java SafeOpener  # optional sanity check (Java 11+ also accepts: java SafeOpener.java)

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Read the source
    Observation
    The challenge gives a Java source file rather than a compiled binary. The encoded password is right there in the source, readable without any reverse engineering.
    The main method simply compares the user input (Base64-encoded) against the constant stored in encodedkey. No reversing needed; just decode.
    What didn't work first

    Tried: Trying to compile and run the Java program and guess the password interactively.

    Running the program and typing guesses gets nowhere, since you do not know the password yet. Read the source first and find where the expected value is stored; the encoded password is sitting there.

    Tried: Assuming the encoded string is encrypted and trying to brute-force or crack it.

    It is easy to mistake Base64 for encryption and start hunting for a key or reaching for cipher tools. Base64 is a reversible encoding with no key at all. A literal whose length is a multiple of 4 and drawn only from the A-Za-z0-9+/ alphabet is the tell, and any decoder recovers the text instantly.

    Learn more

    Source code auditing is the process of reading program source to find security vulnerabilities, hidden logic, or hardcoded secrets. When source is available (as in this challenge), it's far faster than reverse engineering a compiled binary - you can search for keywords like password, key, secret, or encode directly.

    Java source files are particularly readable and widely used in enterprise applications. The openSafe() method pattern - comparing user input against a stored encoded value - mirrors real authentication code that novice developers sometimes write, storing a known-good answer and checking against it rather than using a proper authentication framework.

    The critical insight is that Base64 encoding is not encryption. Storing Base64.encode(password) in source code is functionally identical to storing the plaintext password - anyone who reads the code can reverse it in seconds. Passwords should be stored as salted hashes(bcrypt, Argon2) so that even database breaches don't expose them.

  2. Step 2Format the flag
    Observation
    The decoded Base64 is the password in plaintext, and the description says to wrap it in picoCTF{...}. One formatting step and it is ready to submit.
    Take the decoded password and wrap it as picoCTF{...} to produce the final submission.

    Several lines mention encodedkey, and only one of them holds the literal, so match on the Base64 literal itself rather than on line position:

    $ grep encodedkey SafeOpener.java
            String encodedkey = "";
                encodedkey = encoder.encodeToString(key.getBytes());
                System.out.println(encodedkey);
                if (openSafe(encodedkey)) {
            String encodedkey = "cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz";
            if (password.equals(encodedkey)) {
    
    $ grep -o '"[A-Za-z0-9+/=]\{16,\}"' SafeOpener.java
    "cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz"
    
    $ ... | tr -d '"'
    cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz
    
    $ ... | base64 -d
    pl3as3_l3t_m3_1nt0_th3_saf3

    The final string goes inside picoCTF{...}.

    What didn't work first

    Tried: Submitting the raw Base64 string as the flag instead of the decoded plaintext.

    Having found the encoded key variable, it is tempting to drop that value straight into the picoCTF{} wrapper. The flag wants the decoded text, not the Base64. Run base64 -d, or CyberChef's From Base64, for the actual password.

    Tried: Submitting just the decoded string without the picoCTF{} wrapper.

    The challenge description says to wrap the plaintext inside picoCTF{...}, but it is easy to overlook. Submitting the raw decoded password without the prefix and braces will be rejected - the full flag format is picoCTF{decoded_password}.

    Learn more

    In Java, java.util.Base64 (Java 8+) provides the standard Base64 encoder/decoder. Earlier code used sun.misc.BASE64Decoder, which was internal and non-standard. On the command line, base64 -d(Linux/Mac) or CyberChef's "From Base64" operation decodes the string instantly.

    The pipeline approach used in the setup commands (grep -o | tr -d | base64 -d) demonstrates Unix philosophy: small tools chained together to accomplish a task. Each tool does one thing - grep -o prints just the quoted Base64 literal, tr -d strips the surrounding quotes, base64 -d decodes the result.

    When auditing Java applications professionally, tools like jadx (decompiler), Checkmarx, and SonarQube automate source scanning for hardcoded secrets and insecure patterns across entire codebases.

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
  • Hash IdentifierIdentify unknown hash types by length and prefix. Covers MD5, SHA-1, SHA-256, SHA-512, bcrypt, NTLM, and more.

Flag

Reveal flag

picoCTF{pl3as3_l3t_m3_1nt0_th3_saf3}

Challenge reinforces that storing secrets in client-side code (even encoded) offers no real protection.

Key takeaway

Hardcoded secrets in source are among the most common and most easily exploited vulnerabilities. Base64 and its relatives change how data is represented and provide no confidentiality, because the transformation is public and reversible by anyone. The real-world versions are API keys committed to public repositories, passwords baked into Android APKs, connection strings in decompilable .NET assemblies. Keep secrets out of the codebase entirely: environment variables, a secrets manager, or a hardware key store.

Related reading

Useful tools for Reverse Engineering

Where to go next