Skip to content
UniCircle
MediumConceptualPassword storage

How should a website store passwords? Salting, slow hashes and peppers

Question CYB-CRY-003

Explain how a web application should store user passwords. What is a salt, why must the hash be slow, what is a pepper, and how should the login check work?

Show the explanation

Never store passwords in plain text or with reversible encryption. If the database leaks, every account, and every other site where users reused the password, is exposed.

1. Use a dedicated password-hashing function. Good choices are Argon2id (winner of the Password Hashing Competition and OWASP's first recommendation), scrypt, bcrypt, or PBKDF2 with a high iteration count where compliance requires it.

  • These are deliberately slow and tunable: a work factor, plus memory cost for Argon2 and scrypt.
  • General-purpose hashes like SHA-256 are designed to be fast, so an attacker with GPUs can test billions of guesses per second against a leaked hash.
  • Memory-hard functions (Argon2, scrypt) also make GPU and custom-hardware attacks more expensive.
  • Tune the cost so that one hash takes a noticeable fraction of a second on your server, and raise it over time as hardware gets faster.

2. Salt every password. A salt is a random value, unique to each user (say 16 bytes from a cryptographically secure random generator). It is stored next to the hash and is not secret.

  • Two users with the same password get different hashes.
  • Precomputed tables, such as rainbow tables, become useless.
  • The attacker must attack each hash separately.
  • Argon2, bcrypt and scrypt libraries generate the salt and store it inside the output string for you.

3. An optional pepper. A pepper is a secret value shared by all hashes, kept outside the database, for example in a secrets manager or a hardware security module. It is mixed in with an HMAC or added before hashing. If only the database leaks, the hashes cannot be attacked without the pepper. Rotating a pepper needs planning.

4. Login check

  1. Look up the user's stored hash string, which includes the algorithm, parameters and salt.
  2. Hash the submitted password with those same parameters.
  3. Compare the results with a constant-time comparison. Libraries' verify() functions do this.
  4. If the stored parameters are outdated, re-hash with the new settings now, while you have the plain password.

Around the hashing: rate-limit and monitor failed logins, offer multi-factor authentication, show the same error for "unknown user" and "wrong password", serve everything over TLS, never log passwords, and use a well-tested library instead of writing your own crypto.

Original UniCircle question. Last reviewed 22 Oct 2026.