How Passwords Should Be Stored: Salting, bcrypt, and Why Not SHA-256
When you sign up for a service, it should never keep your actual password. It should keep a value derived from it, one that lets the site check a future login but gives an attacker who steals the database as little as possible. Getting this right is one of the most consequential decisions in application security, and it has a settled answer. Try a password-hashing function with the bcrypt Generator, which runs in your browser.
Try the Bcrypt toolHash a password with bcrypt and verify a password against a bcrypt hash, entirely in your browser.Two things you must never do
Storing passwords in plaintext is the worst case: a single database leak hands every account to the attacker, and because people reuse passwords, it endangers their other accounts too. The less obvious mistake is storing passwords as a plain fast hash such as MD5 or SHA-256. That feels safe because a hash is one-way, but it misses the real threat.
Salt: make identical passwords look different
A salt is a unique random value generated for each password and stored next to its hash. Before hashing, the salt is combined with the password, so two users who both choose the same password still end up with completely different stored hashes. That single change defeats two attacks at once.
- It kills rainbow tables. A rainbow table is a precomputed structure for reversing the hashes of common passwords. A unique salt means the attacker would need a fresh table for every single user, which is no longer worth building.
- It hides reuse. Without a salt, two identical hashes in the database reveal that two people share a password. With per-user salts, that signal disappears.
Slow on purpose: bcrypt, scrypt, and Argon2
Salting stops precomputation, but you still want each individual guess to be expensive. That is what a dedicated password-hashing function adds: it is deliberately slow, with a tunable cost factor you raise over the years as hardware gets faster. A login checking one password barely notices a few hundred milliseconds, but an attacker trying billions of guesses is stopped cold.
| Function | Cost knob | Notes |
|---|---|---|
| bcrypt | Work factor (rounds = 2 to the power of the cost) | Battle-tested, embeds its own salt, limited to ~72 bytes of input |
| scrypt | CPU and memory cost | Memory-hard, which resists cheap GPU and ASIC attacks |
| Argon2 | Time, memory, and parallelism | Modern winner of the Password Hashing Competition; Argon2id is the usual recommendation |
A bcrypt hash is self-describing. A string like $2b$12$... encodes the algorithm version, the cost factor (12 here), and the salt, all before the actual hash. That is why a system can verify a password years later even as you raise the cost for new sign-ups: each stored hash carries the settings it was created with.
How the flow works end to end
- On sign-up, generate a random salt and run the password through bcrypt, scrypt, or Argon2 at a chosen cost. Store only the resulting hash string.
- On login, take the password the user typed and run the same function with the salt and cost read from the stored hash.
- Compare the result with the stored hash. If they match, the password is correct. The original password is never stored or recovered at any point.
An optional extra: the pepper
Some systems add a pepper, a secret value mixed in before hashing that is stored separately from the database, for example in application configuration or a hardware security module. Unlike a salt, a pepper is meant to be secret. The idea is that a stolen database alone is not enough, because the attacker is still missing the pepper. It is a defense-in-depth layer, not a replacement for proper salting and a slow hash.
Experiment safely
The bcrypt Generator hashes and checks values locally in your browser, so you can see how the cost factor and salt change the output without sending anything to a server. For the current, regularly updated recommendations, the OWASP Password Storage Cheat Sheet is the standard reference.
Try bcrypt nowHash a password with bcrypt and verify a password against a bcrypt hash, entirely in your browser.Related articles
Hashing vs Encryption: What a Hash Can and Cannot Do
Hashing is one-way and keyless; encryption is two-way and needs a key. Learn the difference, why you cannot decrypt a hash, and when to use each.
How to Read a JWT, and Why Decoding Is Not Verifying
A JWT is three Base64url parts anyone can read. Learn how to decode one, what each part means, and why decoding proves nothing.
Base64 Explained: Why Encoding Is Not Encryption
What Base64 actually does, why it makes data about a third larger, when to use it, and why it protects nothing on its own.