100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Security

Secure Password Storage

Learn why passwords must never be stored in plaintext or reversible form, and how modern hashing algorithms like bcrypt and Argon2 protect credentials at rest.

Auth & SessionIntermediate8 min readJul 10, 2026
Analogies

Why Plaintext and Encryption Aren't Enough

Storing passwords in plaintext means a single database breach exposes every user's credentials directly, and because so many people reuse passwords across services, that exposure cascades into account takeovers elsewhere. Simple encryption isn't a fix either: if the application can decrypt passwords to check them, an attacker who compromises the app or steals the encryption key can decrypt the entire database just as easily. OWASP's Password Storage Cheat Sheet is unambiguous that passwords must be stored using a one-way, slow, salted hash function — never encrypted and never hashed with a fast general-purpose algorithm like MD5 or SHA-1.

🏏

Cricket analogy: It's like a scorer writing every player's PIN for the pavilion safe directly on the scoreboard for anyone to read — plaintext storage removes any barrier between a casual glance and full compromise.

Hashing, Salting, and Slow Algorithms

A cryptographic hash function like SHA-256 is one-way and fast — which is exactly the problem for password storage, because 'fast' means an attacker with a GPU cluster can compute billions of hashes per second and brute-force through common passwords or precomputed rainbow tables. Salting — appending a unique, random value to each password before hashing — defeats rainbow tables by making precomputed tables useless, since every user effectively has a unique hash space. But salting alone doesn't slow down brute force; that requires deliberately slow, memory-hard algorithms like bcrypt, scrypt, or Argon2, which OWASP currently recommends as the top choice, specifically Argon2id with tuned memory and iteration parameters.

🏏

Cricket analogy: It's like the difference between a bowling machine (SHA-256, fast, mechanical, easy to repeat instantly) and a genuine express fast bowler needing a run-up and recovery time (bcrypt) — the deliberate overhead is the whole point of slowing an attacker down.

javascript
// Node.js: hashing and verifying passwords with bcrypt
const bcrypt = require('bcrypt');

async function hashPassword(plainPassword) {
  const SALT_ROUNDS = 12; // cost factor: higher = slower = more resistant to brute force
  const hash = await bcrypt.hash(plainPassword, SALT_ROUNDS);
  return hash; // bcrypt embeds the salt inside the returned hash string
}

async function verifyPassword(plainPassword, storedHash) {
  return bcrypt.compare(plainPassword, storedHash);
}

// Example: signup
app.post('/signup', async (req, res) => {
  const { email, password } = req.body;
  const passwordHash = await hashPassword(password);
  await User.create({ email, passwordHash });
  res.status(201).json({ message: 'Account created' });
});

Never use MD5, SHA-1, or unsalted SHA-256 for password storage. These are fast general-purpose hashes designed for integrity checks, not credential storage, and modern GPUs can compute tens of billions of SHA-256 hashes per second, making brute force trivial.

Pepper, Parameter Tuning, and Migration

A 'pepper' is a secret value, distinct from the per-user salt, stored outside the database (e.g., in an HSM or environment variable) and applied to every password before hashing; it means that even a full database dump doesn't give an attacker enough to brute-force offline without also compromising the pepper's storage. Choosing Argon2 parameters (memory cost, time cost, parallelism) requires benchmarking on production hardware — OWASP recommends tuning so a single hash takes roughly 250ms–1s server-side, balancing security against login latency and server load. When migrating from a legacy fast hash to bcrypt or Argon2, a common technique is to hash the old hash with the new algorithm on next login, then replace it, avoiding a forced mass password reset.

🏏

Cricket analogy: It's like a team keeping the actual net-practice pitch conditions secret from the database of match statistics — even if opponents get every scorecard (salt+hash), without knowing the pitch secret (pepper) they can't fully recreate the advantage.

OWASP's current guidance ranks algorithms as: Argon2id (preferred), then scrypt, then bcrypt, then PBKDF2 (only when FIPS-140 compliance is required). All four are acceptable when properly parameterized; the key requirement is that the algorithm is deliberately slow and memory-hard, not fast.

  • Never store passwords in plaintext or reversible encryption — always use a one-way hash.
  • Fast general-purpose hashes (MD5, SHA-1, unsalted SHA-256) are unsuitable because attackers can brute-force them at billions of guesses per second.
  • Salting defeats precomputed rainbow tables by making every user's hash space unique.
  • Slow, memory-hard algorithms (Argon2id, bcrypt, scrypt) are required to make brute force computationally expensive.
  • A pepper adds a secret stored outside the database, protecting against a full database dump.
  • Tune hashing parameters (e.g., bcrypt cost factor, Argon2 memory/time cost) to balance security with acceptable login latency.
  • Migrate legacy fast-hashed passwords by re-hashing with a strong algorithm transparently on next login.

Practice what you learned

Was this page helpful?

Topics covered

#Security#WebSecurityOWASPStudyNotes#CyberSecurity#SecurePasswordStorage#Secure#Password#Storage#Plaintext#StudyNotes#SkillVeris