Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Academy Weak Password Hashing
Beginner · 15 min

Weak Password Hashing

Learn why MD5 and SHA1 fail for password storage and how bcrypt and argon2 resist offline cracking.

1 Why MD5 and SHA1 Fail for Passwords

MD5 and SHA1 are general-purpose cryptographic hash functions designed for speed. This speed is catastrophic for password storage — modern GPUs can compute billions of MD5 hashes per second, making offline brute-force trivial.

Vulnerable storage (Python):

import hashlib

# NEVER do this for passwords
hashed = hashlib.md5(password.encode()).hexdigest()
hashed = hashlib.sha256(password.encode()).hexdigest()  # Still wrong!

Why rainbow tables make it worse: Unsalted hashes are vulnerable to precomputed rainbow tables. An attacker who obtains the database can look up millions of common password hashes instantly — no cracking needed.

Speed comparison:

  • MD5: ~10 billion hashes/second on a consumer GPU
  • SHA-256: ~2 billion hashes/second
  • bcrypt (cost 12): ~100 hashes/second
  • argon2id: ~10 hashes/second (configurable)

The 100-million-fold slowdown from bcrypt makes offline brute-force impractical even after a database breach.

2 bcrypt and argon2 — The Right Tools

Password hashing algorithms are specifically designed to be slow (computationally expensive) and include a salt automatically. They are the only acceptable choice for password storage.

bcrypt (Python):

import bcrypt

# Registration
password = b"user_password"
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# rounds=12 means 2^12 iterations — adjust based on server speed
# Target: ~100ms per hash

# Login verification
def verify_login(entered_password, stored_hash):
    return bcrypt.checkpw(entered_password.encode(), stored_hash)
    # checkpw is constant-time — prevents timing attacks

argon2 (Python — recommended for new projects):

from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=3,      # iterations
    memory_cost=65536, # 64MB
    parallelism=4
)

# Hash
hash = ph.hash(password)

# Verify
try:
    ph.verify(hash, password)
except VerifyMismatchError:
    pass  # Wrong password

Defense checklist:

  • Use bcrypt or argon2id — never MD5, SHA1, or bare SHA-256
  • Set work factor so hashing takes ~100ms on your server
  • Never store plaintext passwords or reversible encoding
  • Re-hash on login if work factor needs upgrading

Knowledge Check

0/3 correct
Q1

Why is SHA-256 insufficient for password hashing despite being cryptographically strong?

Q2

What does bcrypt.gensalt() do that prevents rainbow table attacks?

Q3

What is the purpose of the "rounds" (work factor) parameter in bcrypt?

Code Exercise

Replace MD5 with bcrypt

The code hashes passwords with MD5 — trivially crackable. Replace it with bcrypt at rounds=12.

python