Hashing · 06

Password Hashing

SHA-256 is too fast. That sounds like nonsense for hashing in general, but for passwords it is exactly the problem. A modern GPU computes ten billion SHA-256 hashes per second. That makes brute-forcing leaked password databases trivial. Password hashes are designed to be slow on purpose, and a few of them are designed to defeat the parallel hardware that attackers actually use.

01

Why Plain SHA-256 Is Wrong For Passwords

Suppose a database leaks. It contains 100 million rows, each storing SHA-256(password). How bad is this?

An attacker downloads the dump and starts cracking. A single consumer GPU computes about 10 billion SHA-256 hashes per second. Attacking an 8-character password using the typical character set (94 printable ASCII chars) requires at most 94^8 = 6 quadrillion attempts. Divided by 10 billion hashes/sec:

The attacker is not trying random strings against a single password. They are pre-computing hashes of a dictionary of common passwords (and their variations) and comparing against every row in the dump in parallel. A single dictionary run against the entire 100M-row leak completes in seconds. SHA-256 is the wrong tool for the job: its speed is what gives the attacker the advantage.

The 2012 LinkedIn breach

LinkedIn stored 117 million user passwords as unsalted SHA-1 hashes. When the dump leaked, security researchers cracked 90% of the passwords within days using consumer hardware. Many users had reused those passwords on other sites. The cascade of secondary breaches lasted years. The root cause was using a general-purpose hash for password storage, which is fast by design.

02

The Three Things Password Hashes Need

Password hashes are not general-purpose hashes. They have specific design goals:

  1. Slow on purpose. A single hash computation should take 100 milliseconds or more, not microseconds. A legitimate user logs in once and waits a tenth of a second. An attacker trying to brute-force ten billion candidates suddenly faces ten billion tenths of a second.
  2. Memory-hard. The algorithm should require a significant amount of RAM to compute. This penalizes GPU and ASIC attackers, who have lots of compute cores but limited per-core memory. Memory-hard means the speed advantage of specialized hardware shrinks.
  3. Configurable. Hardware gets faster every year. The algorithm should expose tuning parameters (iteration counts, memory size, parallelism) so defenders can keep pace. What was slow in 2015 is fast in 2026.
03

The Workhorses: bcrypt, scrypt, Argon2

AlgorithmYearPropertiesWhere it lives
bcrypt1999CPU-slow, fixed small memory. Cost parameter (work factor).Ruby on Rails, Django defaults until 2019, many legacy systems. Still acceptable.
PBKDF22000 (RFC 2898)Just SHA repeated N times. Not memory-hard. Configurable iteration count.WPA2/WPA3, LUKS disk encryption, browser Web Crypto API. Acceptable when nothing better is available, but not preferred.
scrypt2009Memory-hard. Three parameters: N (cost), r (block size), p (parallelism).Litecoin, Tarsnap, some password managers. Largely superseded by Argon2.
Argon2 (id variant)2015Memory-hard, parallel-resistant. Winner of the Password Hashing Competition.OWASP recommended default. Used by Bitwarden, 1Password, modern Linux logins (libpam-argon2), Signal, Matrix.

OWASP recommendation, current as of 2026: use Argon2id with minimum parameters m=19MiB, t=2, p=1. Fall back to scrypt or bcrypt if Argon2 is unavailable on your platform. Never use plain SHA-256, MD5, or single-round HMAC for passwords.

04

Work Factors and Future-Proofing

Every password hash function takes a tuning parameter that controls how slow it runs. bcrypt calls it the "cost", PBKDF2 the "iteration count", Argon2 has three parameters (m, t, p).

The pattern for defenders: pick parameters that take about 100 to 250 milliseconds on your server's CPU. Re-evaluate annually. When the next CPU generation lands and your hash falls to 60 ms, raise the cost.

Modern stored password hashes encode the parameters inline so you can verify old passwords with their old cost and silently re-hash on the next successful login:

$argon2id$v=19$m=19456,t=2,p=1$IEvxhuekN+8B5RxRMz6XnQ$cMtq8VqcFiRrLeRfb+5gZQ
   ^      ^   ^                ^                      ^
   |      |   |                |                      |
   |      |   parameters       salt (base64)          hash (base64)
   |      |
   algo   version

That single string is everything you need to store. The salt is in there. The parameters are in there. The algorithm is in there. No separate columns required.

05

Why Slow Matters: A Live Timing Comparison

The interactive runs the same password through plain SHA-256 (one round) and PBKDF2-SHA-256 (configurable rounds) and times both. Then it computes how many guesses per second an attacker with a $1,000 GPU could do against each.

Interactive · Hash Speed Comparison

Plain hash vs PBKDF2: timing and attacker throughput

Type a password. Set the PBKDF2 iteration count with the slider. Click Hash. Watch how SHA-256 finishes in microseconds while PBKDF2 takes hundreds of milliseconds. The attacker-throughput numbers translate that time into "guesses per second a GPU adversary could do" against a leaked database storing this format.

Plain SHA-256 (one round)
click "Hash both" to compute
Time on this machine \u2014
attacker throughput: \u2014
PBKDF2-SHA-256 (slow by design)
click "Hash both" to compute
Time on this machine \u2014
attacker throughput: \u2014

Read the difference: PBKDF2 is roughly 100,000x to 1,000,000x slower than plain SHA-256 at the recommended iteration count. That same factor applies to every attacker guess. A dictionary attack that finishes in seconds against plain SHA-256 takes weeks or months against properly tuned PBKDF2. With Argon2id (memory-hard, not browser-available), the slowdown for GPU attackers is even larger because they cannot parallelize as efficiently.

06

Common Mistakes