Description
One of seven candidate passwords matches the stored MD5 hash. Find which one.
Setup
Download level3.py and level3.flag.txt.enc from the challenge page.
Solution
- Step 1Read the candidate list and stored hashOpen 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.
- Step 2Test each candidate with a loopWrite 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. Thedataargument must bebytes, 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
hashcatandjohn(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. - Step 3Run the script with the found passwordExecute 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.