Salting and peppering password hashes
Storing a password means storing something you can check a login against without keeping the password itself. Hashing is the tool, but a bare hash of a password is weak in ways that a salt and a pepper are designed to fix. Getting these right is the difference between a leaked database being an inconvenience and a disaster.
The problem with a bare hash
If you store hash(password) with no salt, two users with the same password get the same stored value. An attacker who steals the database learns which accounts share a password, and can precompute hashes of common passwords once and match them against every row.
Those precomputed tables are called rainbow tables. For an unsalted hash, cracking is not even per user work; one lookup covers everyone who used that password.
What a salt does
A salt is a random value, unique per user, stored alongside the hash. You hash the salt together with the password.
stored = hash(salt + password) // with a unique salt per user
- Two users with the same password now get different stored values, so a leak no longer reveals shared passwords.
- A precomputed rainbow table is useless, because it was built without your salts. The attacker is forced back to attacking each account separately.
The salt does not need to be secret. Its job is to be unique, so storing it in plain text next to the hash is normal and correct.
What a pepper adds
A pepper is also mixed into the hash, but unlike a salt it is secret and the same for all users, and it is kept outside the database, for example in an application config or a hardware security module.
stored = hash(pepper + salt + password) // pepper lives outside the DB
The point is defense in depth. If an attacker steals only the database, they still lack the pepper, so even a correct guess of the password does not produce the stored hash. The salt survives a database leak; the pepper is meant to not be in that leak at all.
Why plain SHA-256 is still wrong
Salt and pepper fix the sharing and precomputation problems, but they do not fix speed. General purpose hashes like SHA-256 are built to be fast, which is exactly what an attacker wants. Modern hardware can try billions of SHA-256 guesses per second, so a stolen salted hash of a weak password still falls quickly.
Passwords need a slow, deliberately expensive key derivation function:
- bcrypt, scrypt, or Argon2 are built for this. They have a cost factor you tune so that one hash takes a noticeable fraction of a second.
- That cost is trivial for a single login but ruinous for an attacker trying billions of guesses.
So the rule is: never store passwords with plain SHA-256, even salted. Use a slow KDF, let it handle the salt, and add a pepper if your threat model warrants it. The comparison in SHA-256 vs HMAC vs bcrypt lays out when each belongs.
Seeing it in practice
To get a feel for how any salt changes an output, the Hash Generator lets you hash text and watch the digest change completely when you prepend a different value, all in your browser so nothing you type is transmitted. Use it to understand hashing, and use a real KDF to store passwords.
The takeaway: salt every password uniquely to kill rainbow tables and hide shared passwords, keep a pepper outside the database for an extra layer, and always reach for a slow KDF rather than a fast general hash.