nicetool.dev logo
#security#hashing#backend

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.

4 min read

"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:

  1. Deterministic - the same input always gives the same output.
  2. One-way - you cannot compute the input from the output.
  3. 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.

The OWASP Password Storage Cheat Sheet currently recommends, in order of preference:

  1. Argon2id with at least 19 MiB of memory, 2 iterations and 1 degree of parallelism.
  2. scrypt with N = 2^17, r = 8, p = 1 if Argon2id is not available.
  3. bcrypt with a cost factor of 10 or more for legacy systems.
  4. 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's verify function, 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.

Try it yourself

Related articles