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

FeatureEncryptionHashing
DirectionTwo-way with a keyOne-way
GoalProtect data that you need to read laterProve that data matches without storing it
Use for passwordsNoYes

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.

AlgorithmNotes
Argon2idFirst recommendation of OWASP; uses memory and time settings
scryptStrong alternative with high memory cost
bcryptWidely supported; limits passwords to 72 bytes
PBKDF2Choice 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

  1. Choose Argon2id when your platform supports it.
  2. Tune the cost settings so one hash takes roughly 100 to 500 milliseconds on your server.
  3. Rehash passwords at login when you raise the settings.
  4. Allow long passwords and passphrases, and permit all characters.
  5. Check new passwords against lists of breached passwords.
  6. Rate limit login attempts to slow online guessing.
  7. 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.