Password Hashes & Cracking in the Lab
By the end of this lesson you will be able to explain how passwords should be stored, show why fast unsalted hashes are weak by cracking hashes you create yourself, and name the right algorithm and settings for safe storage.
This lesson is about defence. You will crack only hashes you generate yourself in your own lab, never real accounts. The Cyber Security lesson Cryptography Basics covers hashing theory; here you apply it to passwords.
Hashing, not encrypting
A hash function turns any input into a fixed-length fingerprint, and you cannot reverse it to get the input back. Passwords should be stored as hashes, so that even if the database is stolen, the real passwords are not simply sitting there.
Think of a tiffin service in Mumbai that writes a code on each box instead of the customer's full address. The code identifies the box, but a thief who steals the code list still does not know where anyone lives. A password hash is that code.
But not all hashing is equal. A plain, fast hash like MD5 or SHA-256 has two problems: identical passwords produce identical hashes, and attackers can try billions of guesses per second.
Problem 1: identical passwords, identical hashes
Run this to see it. Two users with the same password get the same SHA-256 hash:
import hashlib
def sha256_hex(text):
return hashlib.sha256(text.encode()).hexdigest()
users = {
"aarav": sha256_hex("password123"),
"isha": sha256_hex("password123"),
"kabir": sha256_hex("Rohtak@2026"),
}
for name, digest in users.items():
print(f"{name:<6} {digest[:24]}...")
print("aarav and isha match:", users["aarav"] == users["isha"])aarav ef92b778bafe771e89245b89... isha ef92b778bafe771e89245b89... kabir d9f625b5c8c14721063265d1... aarav and isha match: True
An attacker who sees two identical hashes learns that two users share a password, and can crack both at once. The cure is a salt: a unique random value added to each password before hashing, so identical passwords hash differently.
Problem 2: fast hashing is easy to guess
Because SHA-256 is fast, an attacker can precompute hashes of common passwords and look them up instantly. This is a dictionary attack. Here you build a tiny one against hashes you created, to feel how it works:
import hashlib
def sha256_hex(text):
return hashlib.sha256(text.encode()).hexdigest()
# Lab hashes you generate yourself, never real accounts
lab_hashes = {"lab_user1": sha256_hex("summer"), "lab_user2": sha256_hex("Kochi#99x")}
wordlist = ["123456", "password", "summer", "qwerty", "admin"]
lookup = {sha256_hex(word): word for word in wordlist}
for user, digest in lab_hashes.items():
found = lookup.get(digest)
print(f"{user}: {'CRACKED -> ' + found if found else 'not in this wordlist'}")lab_user1: CRACKED -> summer lab_user2: not in this wordlist
lab_user1 used a dictionary word and fell at once. lab_user2 used a longer, unusual password and survived this small list. Real cracking tools such as Hashcat and John the Ripper do this at huge scale against a hash file; you would only ever run them on hashes from your own lab, with permission.
The fix: salt plus a slow hash
Good password storage does two things: adds a unique salt, and uses a slow hash designed to take real time per guess. Slowness barely affects one honest login, but it cripples an attacker trying billions of guesses.
You can feel the idea of "slow" by hashing many times in a loop. This is a teaching model, not for production:
import hashlib
def slow_hash(password, salt, rounds=50_000):
data = (salt + password).encode()
for _ in range(rounds):
data = hashlib.sha256(data).digest()
return data.hex()
print("aarav:", slow_hash("Pune@1234", "salt-aarav")[:24])
print("isha: ", slow_hash("Pune@1234", "salt-isha")[:24])aarav: ce1ddb7779489b638f6cb596 isha: 05a3e700e518a7cd47e05cb4
Two users with the same password Pune@1234 now get different hashes, because their salts differ. And each hash took 50,000 rounds to compute, so guessing is far slower. Real algorithms do this properly; you should never write your own for production.
The real algorithms and settings
Use a purpose-built password hashing function. The OWASP Password Storage Cheat Sheet gives current settings (verify the latest before you rely on them):
| Algorithm | When to use | Example settings |
|---|---|---|
| Argon2id | First choice | memory 19 MiB, iterations 2, parallelism 1 |
| scrypt | If Argon2id is unavailable | N = 2^17, r = 8, p = 1 |
| bcrypt | Legacy systems | work factor at least 10; input limited to 72 bytes |
| PBKDF2 | When FIPS compliance is required | PBKDF2-HMAC-SHA256, 600,000 iterations |
In real code you call a library, not raw hashlib. For example (shown as plain code, since these need extra modules):
# Production: use a maintained library
from argon2 import PasswordHasher # the argon2-cffi package
ph = PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1)
stored = ph.hash("the-user-password") # salt is generated and stored inside
ph.verify(stored, "the-user-password") # raises if wrongA pepper can add one more layer: a secret value, the same for all passwords, kept apart from the database (in a vault or HSM), mixed in before hashing. If only the database leaks, the pepper is not in it. A pepper supports, but never replaces, a per-user salt.
What a tester reports
When you review password storage, you check and report:
- Are passwords hashed at all, or stored in plain text or reversible encryption?
- Is the algorithm a slow, salted one (Argon2id, scrypt, bcrypt, PBKDF2), not MD5, SHA-1 or raw SHA-256?
- Is a unique salt used per password?
- Are settings strong enough (for example bcrypt work factor, Argon2 memory)?
- Is there a minimum password length and a check against common passwords?
Common mistakes
- Using MD5, SHA-1 or raw SHA-256 for passwords. They are far too fast.
- One salt for everyone, or no salt. Salts must be unique per password.
- Writing your own slow hash for production. Use a reviewed library.
- Cracking anything but your own lab hashes. Guessing real accounts is an offence.
Key points
- Passwords are stored as hashes because hashes cannot be reversed to the original.
- Fast, unsalted hashes let identical passwords match and allow fast dictionary attacks.
- A unique salt makes identical passwords hash differently; slow hashing cripples guessing.
- Use Argon2id (or scrypt, bcrypt, PBKDF2) through a maintained library, with strong settings.
- Crack only hashes you create in your own lab, never real accounts.
Exercise
Write a function check_storage(record) where a record has algorithm and salted fields. Return "unsafe" for md5, sha1 or sha256, "ok" for argon2id, scrypt, bcrypt or pbkdf2 when salted, and "needs salt" for an otherwise-good algorithm that is not salted. Test it on three records.
Show solution
SLOW = {"argon2id", "scrypt", "bcrypt", "pbkdf2"}
FAST = {"md5", "sha1", "sha256"}
def check_storage(record):
algo = record["algorithm"].lower()
if algo in FAST:
return "unsafe"
if algo in SLOW:
return "ok" if record.get("salted") else "needs salt"
return "unknown algorithm"
for record in [
{"algorithm": "md5", "salted": True},
{"algorithm": "argon2id", "salted": True},
{"algorithm": "bcrypt", "salted": False},
]:
print(record["algorithm"], "->", check_storage(record))MD5 is "unsafe" whatever the salt, Argon2id salted is "ok", and bcrypt without a salt is "needs salt". A fast hash cannot be rescued by salting alone.
Quick Quiz
1. Why is raw SHA-256 a poor choice for storing passwords?
Password hashing should be deliberately slow; SHA-256 is built to be fast.
2. What does a unique per-password salt achieve?
Salts make each stored hash unique even when passwords are the same.
3. Which algorithm does OWASP recommend first for new systems?
Argon2id is the current first choice, with scrypt, bcrypt and PBKDF2 as alternatives.
4. Whose password hashes may you crack for practice?
Cracking real accounts is unauthorised; practise only on your own lab hashes.
Learn Ethical Hacking with a mentor
Free lessons get you started. Our Ethical Hacking & Cyber Security training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
