Password Security
Proper password handling protects your users even if your database is stolen. The goal is simple: store passwords so that a breach reveals nothing usable, and make online guessing impractical.
Almost every password disaster comes from one of two mistakes — storing passwords reversibly (plaintext or encryption) or hashing them with a fast algorithm. Both are avoidable with a few lines of code.
TL;DR
- Never store plaintext (or reversibly encrypted) passwords.
- Hash with a slow, salted algorithm: Argon2, bcrypt, or scrypt.
- Follow modern NIST guidance: length over arbitrary complexity rules.
- Check new passwords against known breaches (Have I Been Pwned).
- Rate-limit and lock out after repeated failures.
Quick Example
Hash on signup, verify on login — bcrypt salts automatically and bakes in a tunable cost:
Core Concepts
Why hashing (not encryption)
Password hashing is a one-way transformation: you store the hash, and on login you hash the input and compare. There's no key to steal and no way to reverse it. Encryption is reversible, so it's the wrong tool — see Encryption.
Three properties make a password hash safe:
- Salt — a unique random value per user, defeating rainbow tables (modern algorithms add it automatically).
- Work factor — a tunable cost that makes each guess slow.
- Slowness — by design, so offline brute force is expensive.
Choosing an algorithm
Best Practices
Tune the work factor
Set the cost so a single hash takes ~250–500 ms on your hardware, and raise it over time. Support transparent rehashing when you increase it (rehash on the user's next successful login).
Use modern password policies
Current NIST guidance favors length and breach screening over complexity theater:
Throttle and lock out
Common Patterns
Breached-password check (k-anonymity)
Have I Been Pwned lets you check a password without ever sending it — you send only the first 5 characters of its SHA-1 hash and match the rest locally:
Secure password reset
- User requests a reset.
- Generate a cryptographically random token.
- Store only the hash of the token, with a short expiry.
- Email the link containing the token.
- Verify, allow the change, then invalidate the token (single use).
Common Mistakes
Hashing with a fast, general-purpose hash
Enforcing arbitrary complexity rules
FAQ
bcrypt or Argon2?
Both are good. Argon2id is the modern first choice (memory-hard, resists GPU/ASIC cracking). bcrypt is perfectly solid, ubiquitous, and well-supported — keep it if it's already in place.
Should I enforce complexity requirements?
No — modern NIST guidance recommends against mandatory composition rules and periodic forced rotation. They push users toward predictable patterns. Favor a longer minimum length plus a breach check.
Do I still need a salt?
Yes, always — but you don't manage it yourself. bcrypt, Argon2, and scrypt generate and store a unique salt as part of the hash automatically.
What's a "pepper"?
A secret value added to passwords before hashing, stored separately from the database (e.g. in a secrets manager). It's an optional extra layer so that a database-only leak still can't be cracked. It complements salting; it doesn't replace it.
Related Topics
- Authentication — Sessions, tokens, and login flows
- Encryption — Why hashing, not encryption, for passwords
- Secrets Management — Where a pepper would live
- Application Security — The broader threat model