OWASP Password Hashing
Databases leak. Defenders plan for that day by storing passwords in a form that attackers cannot reverse easily. Password hashing turns each password into a scrambled fingerprint, and the application compares fingerprints instead of the passwords themselves.
Hashing Versus Encryption
| Feature | Encryption | Hashing |
|---|---|---|
| Direction | Two-way with a key | One-way |
| Goal | Protect data that you need to read later | Prove that data matches without storing it |
| Use for passwords | No | Yes |
Think of a meat grinder. Beef turns into mince easily, and nobody can turn mince back into a steak. A hash function grinds passwords the same way.
Login Check With Hashes
Sign-up: password → hash function → stored hash
Login: typed password → hash function → new hash
|
compare with stored hash
|
match → access no match → rejected
Why Plain Hashes Fall Short
Fast hash functions such as MD5 and SHA-1 compute billions of guesses per second on modern graphics cards. Attackers hash huge lists of common passwords and compare results with a stolen database. Two users with the same password also share the same hash, which reveals patterns.
Salt: A Unique Ingredient for Each Password
A salt is a random value added to a password before hashing. Every user gets a different salt, so identical passwords produce different hashes. Precomputed tables of hashes become useless. The system stores the salt next to the hash, and good libraries handle this step automatically.
"sunshine" + salt A7f3 → hash 91c0... "sunshine" + salt 02dd → hash e45b... (same password, different result)
Slow Hashing Algorithms
Password hashing needs a deliberately slow, memory-hungry function. The delay costs a legitimate login a fraction of a second and costs an attacker enormous time at scale.
| Algorithm | Notes |
|---|---|
| Argon2id | First recommendation of OWASP; uses memory and time settings |
| scrypt | Strong alternative with high memory cost |
| bcrypt | Widely supported; limits passwords to 72 bytes |
| PBKDF2 | Choice for environments that require certified algorithms; needs a high iteration count |
Code Example
from argon2 import PasswordHasher
ph = PasswordHasher()
stored = ph.hash("correct horse battery staple")
try:
ph.verify(stored, "correct horse battery staple")
print("Login allowed")
except Exception:
print("Login rejected")
The library creates the salt, applies safe default costs, and packs everything into one string.
Pepper: An Optional Extra
A pepper is a secret value stored outside the database, such as in a secrets manager. The application mixes it into the hash process. Attackers who steal only the database still lack the pepper.
Best Practices
- Choose Argon2id when your platform supports it.
- Tune the cost settings so one hash takes roughly 100 to 500 milliseconds on your server.
- Rehash passwords at login when you raise the settings.
- Allow long passwords and passphrases, and permit all characters.
- Check new passwords against lists of breached passwords.
- Rate limit login attempts to slow online guessing.
- Never log passwords or send them by email.
What Happens After a Breach
Suppose attackers steal a database that holds salted Argon2id hashes. Each password needs its own slow guessing effort. Strong passwords survive for years, and weak ones fall first. Users with weak passwords still need protection, so the company should notify users, force resets when needed, and offer multi-factor authentication. Good hashing buys time; it does not replace the other controls.
Key Takeaways
- Hashing stores a fingerprint, not the password.
- Salts make every hash unique.
- Slow, modern algorithms such as Argon2id resist mass guessing.
- Strong policy and rate limits complete the defense.
