Beginner Cryptography

Secure Password Storage

Never store passwords in plain text. Never store only an MD5 or SHA hash. The correct hash function for passwords is a key derivation function (KDF) that is slow by design.

Why MD5 and SHA Are Not Enough

MD5 and SHA-256 are fast — designed for high throughput. A modern GPU can compute billions of SHA-256 hashes per second.

GPU RTX 4090:
  SHA-256:       ~22 billion hashes/second
  bcrypt cost 12: ~2,800 hashes/second

With MD5/SHA, an attacker who steals the database can brute-force common passwords in seconds.

bcrypt

A 1999 algorithm that is still secure. Includes automatic salt and an adjustable cost factor.

import bcrypt

# Hash a password
password = b"my_secret_password"
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# $2b$12$... (salt + hash embedded)

# Verify
bcrypt.checkpw(password, hashed)  # True

The rounds=12 parameter sets the cost (2^12 iterations). Increase it as hardware improves.

argon2

Winner of the Password Hashing Competition (2015). Current OWASP recommendation. Three variants:

  • Argon2d — GPU-resistant, but vulnerable to side-channel.
  • Argon2i — side-channel resistant, less so against GPU.
  • Argon2id — hybrid. Use this one.
from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=3,        # iterations
    memory_cost=65536,  # 64 MB of memory
    parallelism=2
)

hashed = ph.hash("my_password")
ph.verify(hashed, "my_password")  # True

High memory usage makes attacks with specialized hardware (ASICs) impractical.

scrypt

Similar to argon2 in concept. Parameterized by N (CPU/memory), r (block size), and p (parallelism).

import hashlib, os

salt = os.urandom(16)
key = hashlib.scrypt(b"password", salt=salt, n=2**15, r=8, p=1)

Comparison

AlgorithmAuto saltGPU resistanceASIC resistanceOWASP Recommendation
MD5NoWeakWeakNever
SHA-256NoWeakWeakNever (for passwords)
bcryptYesMediumMediumYes
scryptNoGoodGoodYes
argon2idYesExcellentExcellentPreferred

Golden Rule

When verifying a password, always use a constant-time comparison function (checkpw, verify). Never compare strings directly — this is vulnerable to timing attacks.