Why SHA-256 is the wrong tool for passwords
Open ten random tutorials on storing passwords and at least three will show you sha256(password + salt). It looks safe. It is not.
SHA-256 was designed to be fast. A modern graphics card can compute around 50 billion of them per second. If your database leaks, an attacker tries every common password against every user in your database in a single afternoon, and most of the human chosen passwords come out plain.
The salt does not save you. Salts only stop precomputed attack tables. Against a focused attempt on one user, a salt makes no difference at all.
The right answer is a different kind of hash, one designed to be intentionally slow. It does the same job (turn a password into something you can store) but it makes the attacker’s afternoon attack take centuries.
Which algorithm to use
| Algorithm | Memory hard | 2026 verdict | When |
|---|---|---|---|
| Argon2id | Yes | Recommended default | New projects |
| bcrypt | No | Still safe | Existing systems, no rush |
| scrypt | Yes | Acceptable | Already deployed |
| PBKDF2 | No | Last resort | Regulated environments |
| MD5 / SHA-1 | No | Never | No valid use for passwords |
The three options worth using
Three algorithms have passed the tests for production password storage. Each has one reason to pick it and one reason to skip.
Argon2id is the modern default. It is slow on purpose, it requires a lot of memory (which graphics cards do not have much of), and it has a hybrid mode that resists multiple kinds of attack. If you are starting fresh in 2026, this is the one.
bcrypt has been in production since 1999. It is mature, every language has a working library, and the cost knob is intuitive. It uses less memory than Argon2id, which makes it slightly easier to attack with custom hardware, but at a high enough cost factor it still buys real seconds against any realistic attacker. If you have it deployed, do not feel rushed to migrate.
scrypt sits between the two. It is slow and memory hard like Argon2, but its parameters are harder to reason about. Use it if you already have it. Do not pick it for new code unless you have a specific reason.
What is wrong: any plain hash (SHA-256, MD5, BLAKE3), any homemade “iterated SHA-256”, and PBKDF2 unless you are in a regulated environment that forbids the others.
If you only need a checksum or a content fingerprint (not a password), that is the hash generator job, not a password store. Two different problems, two different tools.
Parameters that actually defend in 2026
Default parameters from a 2018 tutorial are calibrated for 2018 hardware. They are too cheap today.
For Argon2id in 2026, a reasonable starting point is around 64 MB of memory, three iterations, and one degree of parallelism. Tune up from there until each hash takes between 250 and 500 ms on your server. That is the sweet spot: slow enough to hurt attackers, fast enough that legitimate logins do not feel laggy.
For bcrypt, a cost factor of 12 is the modern minimum. 13 if your hardware can handle it. The cost factor is exponential, so 13 is twice as slow as 12, and four times as slow as 11.
The single most important rule: measure on your real production hardware, not on your laptop. The right number is the one that takes 250 to 500 ms on the box that will run it.
The pepper question
A pepper is a secret value that you add to every password before hashing, stored separately from the database. If the database leaks but the pepper does not, an attacker has nothing to crack.
Whether to add one is a tradeoff. The pepper has to live somewhere reachable from the application but not the database. Common places: an environment variable, a secrets manager, a hardware module. If you can rotate it cleanly, add one. If your operational story for “where does the pepper live” is shaky, skip it.
The pepper is not a substitute for a strong hash, it is an extra layer.
Migrating without breaking your users
If you have SHA-256 stored today, you cannot rehash everyone instantly because you do not have plaintext passwords. The path is to wrap the new hash around the old one.
On every successful login, you have the plaintext briefly. At that moment, compute the new hash, store it, and mark the user as “migrated”. Over time, every active user gets re-hashed. Users who never log in remain on the old hash, which is fine because if their account never moves, the threat is small.
The wrap-the-old-hash approach lets you start defending immediately, without a flag day where everyone has to reset their password. Most production systems migrate this way and it works.
A practical 2026 setup
Use Argon2id for new systems with the parameters above. Use bcrypt with cost 12+ if you already have it deployed. Add a pepper if you can store and rotate it cleanly. Plan migration if you are still on SHA family or PBKDF2.
When you need to inspect a hash format (which algorithm, what parameters), the hash generator on AldeaCode runs in your browser, with a Node.js variant that mirrors the crypto API and a Python variant for hashlib. The base64 encoder helps when you are decoding a stored hash back to its raw form. The Web Crypto guide covers the difference between data hashing and password hashing in more depth.
Password storage is one of those small disciplines where doing it slightly wrong leaks every account at once. Doing it right is not hard, it just requires picking the slow hash on purpose and tuning it for your hardware. Everything else is detail.