Storing Passwords Safely: Why SHA-256 Is Too Fast and What to Use Instead
Why fast hashes like MD5 and SHA-256 are the wrong tool for passwords, how salts and work factors protect users, and which algorithm (Argon2id, bcrypt, scrypt, PBKDF2) to choose in 2026.
"Just hash the password with SHA-256" sounds reasonable, and it is one of the most common security mistakes in web applications. Hash functions are not all built for the same job. This article explains the difference between general-purpose hashes (MD5, SHA-1, SHA-256) and password hashing functions (bcrypt, scrypt, Argon2), and what you should use today.
What a hash function does
A cryptographic hash function turns any input into a fixed-length fingerprint:
SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("hello!") = ce06092fb948d9ffac7d1a376e404b26b7575bcc11ee05a4615fef4fec3a308b
Good hash functions have three properties:
- Deterministic - the same input always gives the same output.
- One-way - you cannot compute the input from the output.
- Avalanche effect - a one-character change produces a completely different hash.
You can see all three yourself with our Hash Generator: type a word, then change a single letter and compare the results.
General-purpose hashes: MD5, SHA-1, SHA-256
These algorithms were designed to be fast. That is exactly what you want when verifying a 4 GB download or signing millions of API requests.
- MD5 (128-bit) is broken for security: practical collision attacks have existed since 2004. It is still acceptable as a non-security checksum, for example to detect accidental file corruption.
- SHA-1 (160-bit) is also broken for collisions (the public "SHAttered" attack in 2017). Avoid it in new designs.
- SHA-256 / SHA-512 (SHA-2 family) are secure and widely used for checksums, digital signatures, HMAC and blockchains.
Why fast is bad for passwords
Passwords are short and predictable. If an attacker steals your database, they do not need to "reverse" the hash: they simply guess billions of common passwords, hash each guess and compare.
A single modern GPU can compute billions of SHA-256 hashes per second. An eight-character password made of lowercase letters and digits has about 2.8 × 10¹² combinations, which can be exhausted in well under an hour. Leaked-password dictionaries with hundreds of millions of real passwords make it even faster.
So the problem with sha256(password) is not that SHA-256 is weak. It is that SHA-256 is too fast.
Salts: stopping precomputed attacks
A salt is a random value (16 bytes is typical) generated for each user and stored next to the hash:
hash = H(salt + password)
Salts guarantee that two users with the password 123456 get different hashes, and they make precomputed "rainbow tables" useless. But a salt does not slow down guessing a single account. For that you need a work factor.
Password hashing functions
Password hashing functions are deliberately slow and configurable:
| Algorithm | Year | Tunable cost | Memory-hard | Notes |
|---|---|---|---|---|
| PBKDF2 | 2000 | Iterations | No | Built into most platforms, FIPS-approved |
| bcrypt | 1999 | Cost factor (2^cost rounds) | Slightly | Very widely supported, 72-byte input limit |
| scrypt | 2009 | CPU + memory | Yes | Used by some cryptocurrencies |
| Argon2id | 2015 | Time, memory, parallelism | Yes | Winner of the Password Hashing Competition |
"Memory-hard" means each guess needs a significant amount of RAM. GPUs and custom chips have thousands of cores but little memory per core, so memory-hard algorithms remove most of their advantage.
Recommended settings
The OWASP Password Storage Cheat Sheet currently recommends, in order of preference:
- Argon2id with at least 19 MiB of memory, 2 iterations and 1 degree of parallelism.
- scrypt with N = 2^17, r = 8, p = 1 if Argon2id is not available.
- bcrypt with a cost factor of 10 or more for legacy systems.
- PBKDF2-HMAC-SHA256 with 600,000 iterations when FIPS compliance is required.
A good rule of thumb is to tune the cost so that one hash takes around 100-500 ms on your server, then increase it every few years as hardware gets faster.
Example in Node.js
import argon2 from 'argon2';
// Registration
const hash = await argon2.hash(password, { type: argon2.argon2id });
// Store `hash` - it already contains the algorithm, parameters and salt:
// $argon2id$v=19$m=65536,t=3,p=4$c2FsdHNhbHQ$...
// Login
const ok = await argon2.verify(hash, passwordFromUser);
With bcrypt it looks almost the same:
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(password, 12); // cost factor 12
const ok = await bcrypt.compare(passwordFromUser, hash);
Notice that you never manage the salt yourself: modern libraries generate it and encode it inside the output string.
Common mistakes
- Using MD5 or SHA-x directly for passwords, even with a salt.
- Rolling your own scheme, such as
sha256(sha256(password) + secret). Use a vetted library. - Comparing hashes with
==. Use the library'sverifyfunction, which compares in constant time. - Truncating or limiting passwords too much. Allow long passphrases. With bcrypt, be aware that only the first 72 bytes are used.
- Never upgrading. When a user logs in successfully, you can re-hash their password with stronger parameters.
Where MD5 and SHA-256 are still the right tool
- File integrity: publish the SHA-256 checksum of a release so users can verify their download.
- HMAC: sign webhooks and API requests with
HMAC-SHA256(secret, body). Our Hash Generator can compute HMAC signatures to help you debug integrations. - Cache keys and deduplication: hashing file contents to detect duplicates.
- Content addressing: Git, Docker images and many storage systems identify content by its hash.
Conclusion
Use fast hashes (SHA-256) for integrity checks and signatures, and slow, salted, memory-hard functions (Argon2id or bcrypt) for passwords. Never store passwords with MD5 or plain SHA-256, and let a well-maintained library handle salts and parameters for you.
Written by
Nguyễn Thành Nam
Full-Stack Engineer · Backend & Platform
Full-stack engineer with 9+ years of experience building high-traffic APIs and platforms with Go, NestJS, PHP (Laravel, Phalcon) and Node.js. Specializes in GraphQL API design, real-time systems and performance optimization, and builds NICETOOL.dev for the developer community.