The idea: make every guess expensive

A server stores a hash of each password, never the password. When the database leaks, the attacker cannot reverse the hash. It can only guess: hash a candidate password the same way and compare. So the only question is how much one guess costs. The server pays for one hash per login. The attacker pays for one hash per guess, and wants billions of guesses.

Top: the server runs bcrypt cost 12 once per login, 240 ms. Bottom: the attacker runs the same function for every guess in a wordlist on a GPU; at 1,450 guesses per second a top-1M wordlist takes 11.5 minutes, while SHA-256 at 22 billion per second takes 45 microseconds.
The same function runs on both sides; a cost the server pays once, the attacker pays per guess.

On the canvas alice signs up with sunflower7, the table of 1,000 users leaks, and an attacker with one RTX 4090-class GPU (16,384 cores, 24 GiB of memory) tries to crack it. Pick an algorithm, press Leak & crack, and the time bars show how long the GPU needs to try every password in a guess space. Each run adds a row to the scoreboard so you can compare.

Fast hashes and the GPU

MD5 and SHA-256 are general-purpose hashes. They are built to be fast, for checksums and signatures over gigabytes. That speed is exactly wrong for passwords. A GPU has thousands of simple cores, and a SHA-256 guess needs almost no memory, so every core runs guesses in parallel (Demo 1: fast hash).

hashGPU guesses/stop-1M wordlistevery 8-char a-z0-9 password
MD5160 billion6 µs18 s
SHA-25622 billion45 µs2 min
PBKDF2-SHA256, 600,000 iterations15,00067 s6 years
bcrypt, cost 121,45011.5 min62 years
Argon2id, 19 MiB, t=22,0008.3 min45 years

The rates are order-of-magnitude figures for one high-end GPU (hashcat-style benchmarks); a cracking rig has many GPUs, so divide the times by their number. Adding a plain salt to SHA-256 does not change the speed at all (see the salt section below).

The cost factor

A password hash is a slow function with a cost factor the server chooses. It repeats an inner step until one hash takes tens to hundreds of milliseconds:

  • PBKDF2: iterations, the number of HMAC-SHA256 rounds (OWASP: 600,000).
  • bcrypt: cost, a power of two. Cost 12 runs 212 rounds of an expensive key setup. Each +1 doubles the work, so cost 12 is 4× cost 10 for the server and the attacker alike (Demo 3: cost factor).
  • scrypt: N (memory and time), r (block size), p (parallel lanes).
  • Argon2id: m (memory in KiB), t (passes over it), p (lanes).

The parameters are stored with the hash, for example $2b$12$<salt><hash> or $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>. So the server can raise the cost later: after a successful login it has the password in hand, checks the old hash, and stores a new one with the higher cost. Hardware gets faster every year; the cost should follow.

Memory-hardness: idle GPU cores

PBKDF2 is slow on the server but still cheap in memory, so a GPU runs it on every core at once: it does about 3,000× what one server core does (Demo 4: PBKDF2 vs memory-hard). A memory-hard function needs a big block of RAM for every single guess and reads it in a data-dependent order, so it cannot be shortcut by recomputing. scrypt with N=217 needs 128 MiB per guess; Argon2id with m=19 MiB needs 19 MiB. A 24 GiB GPU fits only 192 or 1,293 such guesses at once. Most of its 16,384 cores sit idle, and custom chips (ASICs) gain little because memory is the expensive part.

Three GPU core grids. PBKDF2: all 16,384 cores busy, 15,000 guesses per second. Argon2id 19 MiB: VRAM full after 1,293 guesses, about 8 percent of cores busy. scrypt 128 MiB: 192 guesses, about 1 percent busy.
Memory per guess, not the number of cores, decides how many guesses a GPU can run at once.

bcrypt sits in between: it needs only 4 KiB, but it reads and writes that table constantly, which GPUs handle badly, so it is far slower on a GPU than its tiny memory suggests.

Salt: pay per user

A salt is a random value stored with each user's hash. It does not slow down one guess. It stops one guess from serving every user. Without a salt, the attacker hashes password1 once and looks the result up in all 1,000 rows; with a salt, it must hash it 1,000 times, once per salt. On the canvas the "wordlist vs all 1,000 users" bar is the same as one user for SHA-256 and 1,000× longer for SHA-256 + salt (Demo 2: salt and the whole table). bcrypt, scrypt, Argon2 and PBKDF2 always take a salt. Where the salt lives, and how a secret pepper stops a database-only leak, is on Salt vs Pepper: Storing Passwords.

Picking parameters

choiceOWASP minimumnote
Argon2idm=19 MiB, t=2, p=1first choice; or m=46 MiB, t=1
scryptN=217, r=8, p=1when Argon2id is not available
bcryptcost 10legacy systems; input limited to 72 bytes
PBKDF2-HMAC-SHA256600,000 iterationswhen FIPS-140 compliance is required

Then raise the cost until one login takes as long as your servers can afford (often 100–500 ms). A slow hash is also a cost for you: a flood of login requests burns CPU and memory, so put a rate limit in front of it.

What a slow hash cannot fix

A slow hash buys time; it does not make a weak password strong. Even under Argon2id, a password in a top-1M breach list falls in minutes on one GPU, because the attacker tries the most common passwords first (Demo 5: MD5 vs Argon2id). What helps there: rejecting passwords found in breach lists at sign-up, rate limits and lockouts on the login form, and a second factor (see OWASP Authentication Failures). Why passwords are hashed rather than encrypted is on Encryption vs Hashing, and how SHA-256 works inside on Inside AES-128 and SHA-256; unsalted MD5 as a real bug is in OWASP Cryptographic Failures.

in "The idea" or "The cost factor": "Proof of work uses the same asymmetry on purpose, costly to compute and cheap to check: Proof of Work.

What the page leaves out

The GPU rates and server times are fixed, rounded figures for one GPU and one CPU core; real numbers depend on the hardware, the implementation and the parameters, and a real attacker rents many GPUs. Hashes and salts are shortened toy values. The page also skips cracking rules (mangling, masks, Markov models), rainbow tables in depth, peppers, time-memory trade-off attacks on scrypt and Argon2i, Argon2's lanes and threads, bcrypt's 72-byte limit and pre-hashing, and login-side defences.