PW Crack 3

Published: April 2, 2026

Description

One of seven candidate passwords matches the stored MD5 hash. Find which one.

Download level3.py and level3.flag.txt.enc from the challenge page.

Solution

  1. Step 1Read the candidate list and stored hash
    Open level3.py -- it contains a list of seven candidate passwords and an MD5 hash string that the correct password must produce.
    Learn more

    This challenge introduces the concept of password hashing. Rather than storing the password directly, the script stores an MD5 hash of the correct password. To verify a user's input, the script hashes what they typed and compares the result to the stored hash -- the original password never needs to be stored or compared directly.

    MD5 produces a 128-bit hash represented as 32 hexadecimal characters. The same input always produces the same output (deterministic), but even a single character change in the input produces a completely different hash (the avalanche effect). This makes it easy to verify passwords without storing them in plaintext.

    The weakness here is the small candidate list -- with only 7 possible passwords, checking each one is trivial. This is the foundation of a dictionary attack: instead of trying every possible input (brute force), you test a curated list of likely passwords against the stored hash.

  2. Step 2Test each candidate with a loop
    Write a short Python loop that hashes each candidate with hashlib.md5 and compares the hex digest to the stored hash. The correct password is dba8.
    python3 -c " import hashlib hash_val = '...stored_hash...' candidates = ['ee42','3a5f','a3de','dc42','dba8','a775','4dbe'] for pw in candidates: if hashlib.md5(pw.encode()).hexdigest() == hash_val: print('Password:', pw) "
    Learn more

    hashlib is Python's standard library for cryptographic hashing. The call hashlib.md5(data) creates an MD5 hash object; calling .hexdigest() on it returns the lowercase hex string. The data argument must be bytes, so string passwords require .encode() (which defaults to UTF-8).

    The loop pattern here is the essence of a dictionary attack:

    • Take each candidate from the list
    • Hash it with the same algorithm used to create the stored hash
    • Compare the result to the target hash
    • Stop when a match is found

    The same logic powers tools like hashcat and john (John the Ripper), which can test billions of candidates per second using GPUs. The only difference is scale -- the algorithm is identical to what you are writing here.

  3. Step 3Run the script with the found password
    Execute level3.py and enter dba8 when prompted. The flag is decrypted and printed.
    python3 level3.py
    # Enter password: dba8
    Learn more

    With the correct password in hand, the script's XOR decryption routine unlocks the encrypted flag. This two-step structure -- hash verification then decryption -- mirrors how real password-protected systems work: the password is first verified (by hashing), then used to derive a decryption key for the actual protected data.

    Why MD5 is no longer secure for passwords: MD5 is fast -- a modern GPU can compute billions of MD5 hashes per second. This means a dictionary or brute-force attack against MD5-hashed passwords can succeed very quickly. Modern password storage uses deliberately slow algorithms like bcrypt, Argon2, or PBKDF2, which make each hash computation expensive and slow down attacks by many orders of magnitude.

Flag

picoCTF{...}

With only 7 candidates, even manual testing is feasible -- but scripting it demonstrates the dictionary attack approach used in real-world password cracking at scale.

More General Skills