Skip to content
KEDBYTE
How Identity Works
Chapter
15

Cracking a Password

Part II · Proving It Is You|9,986 words|about 43 min read|Volume 2

15.0 What this chapter gives you#

  1. You will be able to describe exactly what an attacker holds the morning after a password database is stolen, and why that is a different problem from guessing at a login screen.
  2. You will be able to name the four shapes of a guessing attack, brute force, mask, dictionary and rule-based mangling, and say which one an attacker reaches for first and why.
  3. You will be able to explain a rainbow table as a trade of memory for time, work a small chain by hand, and say in one sentence why a salt destroys it.
  4. You will be able to quote real cracking rates for MD5, SHA-256, bcrypt at cost 12 and Argon2id, and say what hardware and what date each number belongs to.
  5. You will be able to turn those rates into money, hashes per second per pound of hardware, and cost per trillion guesses on rented machines.
  6. You will be able to say precisely why a memory-hard function raises an attacker’s cost when a slow-but-small one does not.
  7. You will be able to explain the online attack, why rate limiting and lockout help, and how a lockout rule becomes a way to attack the honest user instead.
  8. You will be able to check a password against a list of half a billion breached ones without sending the password anywhere, and state the exact limit of that privacy claim.
  9. You will be able to take one ordinary nine-character password and follow it, with real numbers, from a stolen row to either a millisecond or a fortnight of an attacker’s time, depending only on how it was stored.

This chapter is the other side of the one before it. The previous chapter, “Storing a Password”, built a defence in layers and told you that each layer answers a specific attack. This chapter is those attacks. You cannot judge whether a storage choice is good or wasteful or reckless until you can put a number on what it costs the person trying to break it, and that number is the whole subject here. A storage decision that looks careful can be worthless, and one that looks paranoid can be exactly right, and the only way to tell them apart is to do the attacker’s arithmetic yourself.

The heart of the matter is an asymmetry. When a database of hashed passwords is stolen, the defender has already lost the fight for secrecy of the file. What remains is a race: the defender has arranged, in advance, for each guess to cost the attacker a certain amount of work, and the attacker now spends money and time buying that work in bulk. Good storage does not make the race unwinnable. It makes it expensive enough that the attacker gives up on most accounts and goes elsewhere. Every figure in this chapter is really a figure about that race.

We will keep one honest discipline throughout. Cracking numbers are marketing catnip, and the internet is full of “any password cracked in seconds” claims that quietly assume the worst possible storage. Every rate in this chapter names the hash function, the hardware, the tool version and the date it was measured, because a rate without those four things is a rumour. Where a number is derived by arithmetic rather than measured directly, we say so and show the arithmetic.

One boundary before we start. How to store a password well, salt, pepper, key derivation functions and their cost parameters, is the previous chapter’s job, and how to choose and require passwords, length, blocklists and the rules that were withdrawn, is the next chapter’s job. This chapter touches both only far enough to attack them. When we need a stored record to break, we take one; we do not re-teach how it was built.

The plain version#

A stolen notebook of locked boxes#

Picture the club from the door of the previous chapter. There is one door, one register, and instead of writing each member’s secret phrase in plain ink, the doorman has been careful. When you join, he does not write your phrase. He puts your phrase through a little machine that turns any phrase into a five-digit number that cannot be run backwards, and he writes the number. Your phrase “sunflower” becomes 48211, and 48211 is what sits in the register beside your name.

Now the register is stolen. Not peeked at, not photographed at the door, but lifted whole and carried off. What does the thief have? Not phrases. A long list of names, each beside a five-digit number that reveals nothing on its own. He cannot read your phrase out of 48211 any more than the doorman could, because the little machine only runs one way.

So the thief does the only thing left. He sits at home with an identical copy of the little machine, and he starts feeding phrases into it. He tries “password” and gets 71034; no name in the register has 71034, so nobody used “password”. He tries “sunflower” and gets 48211, and now he knows every member whose number is 48211 chose “sunflower”. He is not reading the register. He is guessing phrases, transforming each one, and looking for the answer he already holds.

This is the whole of an offline attack, and almost everything real crackers do is a refinement of it. The word “offline” is the important part. The thief is not at the club door. Nobody sees him. There is no doorman to count his tries, no lock to jam after three wrong ones, no alarm. He can guess as fast as his machine runs, for as long as he likes, in complete privacy, until the register is meaningless. The defence can no longer be “stop him guessing”, because there is no longer anyone in the room. The only defence left is to make each guess slow, and to make sure a phrase guessed for one member does not also unlock another.

Guessing is just trying, very fast#

How does the thief choose which phrases to try? Not at random, that would be hopeless, and he knows it. He tries in order of likelihood, and people are wonderfully predictable, so the order is short at the top.

He starts with a list. Not a list he invented, a list handed to him by history: the real phrases that real people chose, taken from earlier thefts, sorted by how often they came up. “123456” first, then “password”, then “iloveyou”, down through a few million entries. This is a dictionary attack, and the word dictionary is a little misleading, because the best dictionaries are not English words at all, they are the passwords humans have actually been caught using. Feed the whole list through the machine, compare every result against the stolen register, and in a few minutes the thief has every member who chose a phrase anyone has ever chosen before. On a real stolen file that is usually most of it.

For the members who survive that, the thief gets cleverer. He notices that people do not just use “summer”, they use “Summer”, “summer1”, “Summer2024”, “summer!”, “$ummer”. So he takes his list and mangles it by rules: capitalise the first letter, add a digit on the end, add a year, swap an “a” for an “@”. One base word becomes a hundred variations, all tried automatically. This is rule-based mangling, and it is the single most productive technique in real cracking, because it captures exactly the small, lazy edits people make when a website demands “at least one capital and one number”.

Only when the lists and the rules are exhausted does he fall back to blind trying: every possible phrase of length one, then length two, and so on, “aaaa”, “aaab”, “aaac”, forever. This is brute force, and it is the slowest and stupidest method, used last and only against short phrases, because the number of possibilities explodes as the phrase gets longer. If he knows something about the shape, that the phrase is exactly a capital, three lowercase letters, a symbol and four digits, he can brute force only that shape and skip the rest. That narrowing is called a mask, and it turns an impossible search into a quick one when the shape is guessable, which, after a website’s rules have herded everyone into the same shape, it very often is.

The pre-made answer book#

There is a trick that sounds like cheating and, for a while, nearly was. What if, instead of guessing phrases when the register is stolen, the thief did all the guessing in advance, once, for everybody, and wrote the answers in a giant book?

Imagine a book with two columns: on the left every phrase, on the right the number the little machine turns it into. When a register is stolen, the thief does not compute anything. He looks up each stolen number in the right-hand column and reads off the phrase. Instant. All the slow work was done years ago and is now just a lookup.

The catch is size. To cover every phrase up to a useful length, that book would be so large it could not be stored, larger than every hard drive ever made. So a clever compromise was invented: instead of storing every phrase and its number, you store only the two ends of long chains of phrases, and rebuild the middle when you need it. It trades away most of the storage in exchange for a little computation at lookup time. This is a time-memory trade-off, and the polished version of it is called a rainbow table. It let people, around the year 2003, crack certain Windows passwords in seconds from a couple of CD-ROMs, and it terrified everyone who understood it.

And then it was killed, cheaply and permanently, by one idea. Before the little machine transforms a phrase, the doorman glues a short random tag to the front of it, a different tag for every member, and stores the tag in the open beside the number. Now “sunflower” for one member is really “9f3k:sunflower” and for another is “q2xz:sunflower”, and the two produce completely different numbers. The pre-made answer book is worthless, because it was built for “sunflower”, not for “9f3k:sunflower”, and the thief would need a separate book for every possible tag, which is exactly the mountain of storage the trick was invented to avoid. That random tag is a salt, and defeating the answer book is the first and simplest thing it does. The salt is not secret, it sits in the stolen file in plain view; it does not need to be secret to work.

Why a slow lock changes everything#

Everything above assumed the little machine is fast, and that is the real vulnerability. If turning a phrase into a number takes a millionth of a second, the thief can try billions of phrases while the kettle boils.

So the good doormen use a deliberately slow machine. Not slow by accident, slow by design: a machine that takes, say, a quarter of a second to transform one phrase, because it repeats the work many thousands of times before giving an answer. For you, logging in once, a quarter of a second is nothing, you do not notice it. For the thief trying to grind through billions of guesses, a quarter of a second each is a wall. The same guessing that took an afternoon against the fast machine would take longer than his lifetime against the slow one.

There is a stronger version still. Make the machine not just slow but greedy for memory, so that each single transformation needs a big scratchpad, tens or hundreds of megabytes, to do its work. A normal computer has plenty of memory to do this once for one login. But the thief’s whole advantage is that he owns special hardware that does thousands of transformations side by side at once, and thousands of big scratchpads will not fit. Demanding memory takes away his parallel-machine advantage specifically. This is memory-hardness, and it is the current best defence, the reason the newest recommended machine is measured in megabytes as well as milliseconds.

So the plain version ends with a simple ladder. A fast machine loses to a stolen register in minutes. A salt kills the pre-made answer book. A slow machine turns minutes into years. A memory-greedy machine takes away the thief’s special hardware. Each rung costs the honest user almost nothing and costs the attacker enormously, and the whole of the technical half is putting exact numbers on “enormously”.

The password we will follow to the end#

We will carry one real example the whole way. Meera lives in Pune and shops at a grocery delivery service. Her password there is “Pihu@2019”, her daughter’s name, the symbol the site insisted on, and the year she signed up. Nine characters, a capital, lowercase, a symbol and digits: every strength meter she has met calls it strong.

One night the grocery service is breached and its whole user table is stolen. In the plain telling, Meera’s fate now depends entirely on one decision the grocery service made months earlier and never thought about again: which little machine it used. If it used the fast one, her password is recovered before she finishes dinner. If it used the slow, salted, memory-greedy one, the thief takes one look at how long “Pihu@2019” will cost him and, for a database of eight million people, quietly decides she is not worth it and moves to the easier accounts. Same password, same person, same theft; the only variable is storage. In the technical half we replace “before dinner” and “not worth it” with seconds, days and pounds.

Where the plain version stops being true#

The attacker does not guess in the dark#

The plain version has the thief trying phrases roughly in order of popularity. A real attacker is far better informed than that, and pretending otherwise flatters our defences.

He does not guess Meera’s password from nothing. He very often already has one of Meera’s old passwords, from some other breach, and people edit rather than replace, so “Pihu@2018” is a strong hint toward “Pihu@2019”. He knows the site’s own rules, so if the site demands a symbol he does not waste a single guess on phrases without one. He knows the shape that rules produce, capital-word-symbol-year is a national habit, not Meera’s private idea. And he trains his mangling rules on the very breach he is cracking, so the rules get better as he goes. The honest picture is not a man reciting a dictionary; it is a targeting system that narrows the search using everything already known about how humans respond to password rules.

The honest version: the useful measure of a password is not how many phrases exist of its length, but how early a well-informed attacker, guessing in the smartest order, would reach it. Those two numbers can differ by many orders of magnitude, and the gap between them is where most account losses live. The next chapter, on why password rules are mostly wrong, is largely the story of policies that widened that gap while claiming to close it.

“Cracked” is not all-or-nothing#

The plain version speaks as though a stolen database is either broken or safe. It is neither; it is a percentage that climbs over time, and the shape of that climb is the thing that actually matters.

Against a fast hash, the curve leaps: the dictionary and rules recover the easy majority in the first hours, then the curve flattens as only the genuinely strong passwords remain, and those trickle in over weeks or never. Against a slow, salted hash the same curve exists but is stretched out enormously, so that the “first hours” become “first months” and most attackers stop long before the flat part. So when a company announces that “passwords were hashed”, the honest question is never “were they cracked” but “what fraction, how fast, and did the attacker bother to finish”. A slow hash rarely makes cracking impossible. It makes the attacker stop early, and stopping early means the strong-password users are never reached. The value of good storage is measured in the accounts the attacker never gets to.

The 2015 Ashley Madison breach is the sharpest illustration, because it shows both halves of the truth. The site stored its roughly 36 million passwords with bcrypt at cost 12, a genuinely strong choice, and the flat part of the curve held: a researcher brute-forcing the bcrypt hashes recovered only about 4,000 in five days, well under a tenth of a per cent. Then the cracking team known as CynoSure Prime read the leaked source code and found that, for accounts created before a certain date, a secondary token had been computed as a plain MD5 of the username and the password. That fast, unsalted side door let them recover about 11.2 million passwords in days, not by breaking bcrypt at all but by walking around it. The lesson is exact: storage is only as strong as its weakest path, and a single fast-hash mistake elsewhere in the system can undo a careful slow hash completely.

Salt is public, and there is a secret cousin#

The plain version might leave you thinking the salt is the secret ingredient. It is not, and this trips up newcomers constantly. The salt sits in the stolen file in the clear, one per user, and it is meant to. Its job is not to be unknown; its job is to be different for every user, so that no pre-made table and no single cracking pass can serve two accounts at once. Knowing a user’s salt does not help the attacker guess that user’s password one bit faster.

There is a genuinely secret ingredient, a keyed value kept outside the database so that it is not stolen along with it, and it does raise the attacker’s cost when only the database leaks. That is the pepper, and it belongs to the storage chapter, which sets out exactly what it defends and what it costs to operate. We name it here only so you do not confuse it with salt: salt is public and per-user; pepper is secret and system-wide.

A slow hash does not rescue a weak password#

Here is the correction that surprises people most, and it is the bridge to the next chapter. A slow, salted, memory-hard hash is a defence of the storage, not of the password. It multiplies the attacker’s cost per guess by a large constant. If the password itself needs only a few billion guesses to reach, a large constant times a few billion is still, on a rented rig, an affordable bill.

We will see this exactly with Meera. “Pihu@2019” against a good modern hash does not survive forever; it survives about a day and a half of a serious rig, because its shape is guessable and the whole keyspace of that shape is only a few billion. Good storage bought her a day and a half instead of a millisecond, which for a mass breach is often enough to save her, because the attacker is busy with easier accounts. But it did not make her safe; a genuinely unguessable password would have made her safe under any of these hashes, and a guessable one is at risk under all of them. Storage and password strength are two different defences against two different attacks, and neither substitutes for the other.

Memory, not time, is the current battlefield#

The plain version treats “slow” and “memory-greedy” as two flavours of the same idea. They are not, and the difference is the whole reason the newest functions look the way they do.

Making a hash slow by repeating work still leaves the attacker’s core advantage intact: he owns hardware, graphics processors and custom chips, that does thousands of these repetitions in parallel far more cheaply than your laptop does. Slow-but-small functions have been steadily eroded because that hardware kept getting faster and cheaper. Making a hash need a large block of memory per guess attacks the advantage itself, because fast parallel hardware is precisely the thing that is starved for memory when you run thousands of copies at once. The frontier of password storage moved from “how many rounds” to “how many megabytes” for this single reason, and the technical half shows the cost curve that forced the move.

The technical version#

The offline attack model, stated precisely#

Assume the worst that is still realistic. An attacker has obtained a full copy of the authentication table: for each account, a username or email, and a stored verifier. He is offline, meaning he computes on his own hardware with no interaction with the victim service, so no rate limit, lockout, logging or network latency applies. He can make as many guesses as he can afford. This is the model every storage decision is really made against, and it is the model the whole first half of this chapter described in plain words.

What exactly does he learn from a stored verifier? Modern verifiers are self-describing: the record names the algorithm and its parameters in the open, because the honest server needs them to check your next login, and a stolen record therefore hands them to the attacker too. A bcrypt record and an Argon2id record look like this, and every field before the final hash is readable:

$2b$12$R9h/cIPz0gi.URNNX3kh2O
   PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW

$argon2id$v=19$m=65536,t=3,p=1
   $c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJ

From the first the attacker reads “bcrypt, cost 12”. From the second he reads “Argon2id, version 19, 65536 kibibytes of memory, 3 passes, 1 lane”, then the salt, then the hash. He now knows precisely how much work each guess will cost him, which is why he can plan the economics before he starts. Note what he does not learn: the password. The one-way property, the same property the storage chapter defines as preimage resistance, means the only route from the hash back to the password is to guess a candidate, run it forward through the named function with the named salt and parameters, and compare. The entire attack is that loop, run as fast as money allows:

for each candidate guess g:
    h = KDF(g, salt, params)   # the slow part
    if h == stored_hash:
        report g; stop
    else:
        next

Everything that follows, dictionaries, masks, rules, rainbow tables, GPUs, is about making that loop cover the likely candidates as cheaply as possible, and everything the defender does is about making each turn of the loop cost more.

The four shapes of a guessing attack#

Real cracking tools, of which the dominant free one is hashcat, now at version 7.0.0 released on the first of August 2025 with 7.1.2 following on the twenty-third of August 2025, organise guessing into a small number of attack shapes. Four cover almost everything.

A brute-force or mask attack enumerates a keyspace defined by a pattern. Hashcat writes patterns with placeholders: ?l is a lowercase letter, ?u uppercase, ?d a digit, ?s a symbol, ?a any of those, and ?b any byte. A mask spells out a shape and its size is the product of the placeholder sizes. Meera’s “Pihu@2019” fits the mask ?u?l?l?l?s?d?d?d?d, a capital, three lowercase, a symbol, four digits, and its keyspace is the product of the parts:

?u ?l ?l ?l ?s ?d ?d ?d ?d
26 26 26 26 33 10 10 10 10
= 26^4 * 33 * 10^4
= 456,976 * 33 * 10,000
= 150,802,080,000 candidates

If the attacker guesses that the symbol is specifically “@”, he fixes it and the mask shrinks to ?u?l?l?l with a literal “@” and ?d?d?d?d, which is 26^4 times 10^4, or 4,569,760,000 candidates, under five billion. Hold that number; it is Meera’s whole exposure, and we will spend it against four different hashes shortly.

A dictionary or wordlist attack runs a fixed list of candidate passwords, typically real passwords from prior breaches sorted by frequency, rather than an abstract keyspace. It is the first thing any attacker runs because its hit rate per guess is enormous: a good breach-derived wordlist recovers the easy majority of most stolen tables in the time it takes to read the list once.

A rule-based attack applies transformation rules to each dictionary word on the fly, so one base word becomes many candidates. Hashcat’s rule language is a small string-editing alphabet: c capitalises the first letter, $X appends the character X, ^X prepends it, sXY substitutes X with Y, r reverses, d duplicates. A rule like c $2 $0 $2 $4 turns “pihu” into “Pihu2024”. Hashcat ships rule files; the widely used best64 set is a curated collection of the sixty-four most productive such rules, and larger sets run to many thousands. Rules are the most productive technique against human passwords precisely because they encode the small edits that composition rules force people to make.

A combinator or hybrid attack joins wordlists together or bolts a mask onto a word, capturing patterns like two words stuck together or a word followed by a year. These four shapes, blind enumeration, known lists, mangled lists, and joined lists, in roughly that order of productivity, are the entire offensive toolkit, and the defender never gets to choose which one is used; the attacker uses whichever reaches the password soonest.

Rainbow tables and the time-memory trade-off#

Before salts were universal, the sharpest offline trick was to move all the work before the theft. The idea traces to Martin Hellman’s paper “A Cryptanalytic Time-Memory Trade-Off” in the IEEE Transactions on Information Theory, volume 26, number 4, pages 401 to 406, in 1980, and its practical high point is Philippe Oechslin’s “Making a Faster Cryptanalytic Time-Memory Trade-Off” at CRYPTO 2003, in Springer’s Lecture Notes in Computer Science volume 2729, pages 617 to 630.

The trade-off works like this. Storing every password next to its hash would be a perfect lookup table but is far too large. Storing nothing and computing on demand is a pure brute force, cheap in memory and dear in time. Hellman found the middle: build long chains. Start from a password, hash it, then apply a reduction function that maps the hash back down to some other password in the space, hash that, reduce again, and so on for thousands of steps. Store only the first and last password of each chain. You have thrown away the entire middle of every chain, saving almost all the memory, at the price that finding a target hash now means recomputing a chain’s middle to locate it.

Oechslin’s improvement, the rainbow table proper, uses a different reduction function at each step along the chain, which removes most of the merging and false alarms that plagued the older method and made it faster and more reliable. His own demonstration is the classic figure to remember: attacking Microsoft Windows LM password hashes, using 1.4 gigabytes of tables, two CD-ROMs, he recovered 99.9 per cent of all alphanumeric passwords, a keyspace of about 2 to the 37th power, and did it in 13.6 seconds where the older distinguished-points method took 101 seconds. A picture of one chain, hash and reduce alternating, with only the ends kept:

start  ->  H  ->  hash  ->  R1 ->  pw2 -> H -> ... -> end
"aaaaaa"    9f2c..        "koala1"                    "zturtz"
  ^ stored                                       stored ^
  everything between the two ends is thrown away
  and recomputed only when a target must be found

Two real tools made this practical: RainbowCrack, built by Zhu Shuanglei around 2003, and Ophcrack, Oechslin’s own Windows cracker, which shipped downloadable tables that let anyone with a laptop recover most Windows passwords of the era in minutes.

Why salt kills the trade-off#

A rainbow table is built once, for a given hash function, over a given keyspace, and then reused against every stolen hash of that function. Its entire economic case is that one expensive build amortises across millions of victims. Salt removes that amortisation, and with it the whole point.

A salt is a random value, unique per password, mixed in before hashing, and stored in the clear beside the hash. Its effect on precomputation is total. To use a rainbow table against a salted hash, the attacker would need a separate table for each possible salt value, because the salt changes what “the hash of sunflower” even is. With a salt of any real length the number of possible salts is astronomical: bcrypt uses a 128-bit salt, so there are 2 to the 128th possible salted variants of every password, and building even one rainbow table per salt is many times more work than simply guessing each account directly. Precomputation stops being cheaper than live guessing, so the attacker abandons it and goes back to the guess loop, now forced to redo all his work separately for every single account.

That last point is worth stating plainly, because it is salt’s second and quieter benefit. Even without any rainbow table, an unsalted file lets the attacker test one guess against all accounts at once: hash “123456” a single time, compare to every row. Salt forbids this. Because every row has a different salt, “123456” must be hashed afresh for each account, so the attacker’s work scales with the number of accounts. Salt does not slow a single guess against a single account at all; what it does is destroy precomputation and destroy cross-account sharing. The small table below separates what salt does and does not do, because the two are constantly confused.

Attack Salt’s effect
Rainbow or precomputed table Defeated completely
One guess tested on all rows Defeated completely
Guessing one account’s hash No effect at all
A weak password No effect at all

Salt is therefore necessary and nowhere near sufficient. It closes the shortcuts. It does nothing about the raw speed of the underlying hash, which is the subject of the next three sections and the reason a fast salted hash still falls fast.

The hardware, and its cost in pounds#

The attacker’s guess loop runs on hardware built for exactly this shape of work, and to judge storage you need to know what that hardware costs and what it delivers. The three tiers are ordinary processors, graphics processors, and custom silicon.

Graphics processors are the workhorse of password cracking because a fast hash is embarrassingly parallel: thousands of independent guesses, each a short arithmetic recipe, is precisely what a graphics chip is built to run. This is not new. In December 2012, at the Passwords^12 conference in Oslo, Jeremi Gosney demonstrated a single cluster of 25 graphics cards that computed about 348 billion NTLM and 180 billion MD5 guesses a second, enough to exhaust every eight-character password of 95 possible symbols, 95 to the 8th, in about 5.5 hours, while the same rig managed only about 71,000 bcrypt guesses a second. That fourteen-year-old machine already spelled out the whole moral: fast hashes fall to hardware, and bcrypt did not, by a factor of millions. The published hashcat benchmark for a single NVIDIA GeForce RTX 5090, run in February 2025, is the modern reference point for this chapter, and its numbers are the raw material for everything that follows.

Algorithm Rate, one RTX 5090 Note
NTLM 340.1 GH/s Windows, fast
MD5 220.6 GH/s fast, broken
SHA-256 28.35 GH/s fast, general
md5crypt 87.4 MH/s 1000 rounds
sha512crypt 1.48 MH/s 5000 rounds
bcrypt cost 5 304.8 kH/s benchmark cost

Read the size of the drop. The same card that computes 220,600,000,000 bare MD5 hashes every second manages only 304,800 bcrypt hashes at the benchmark cost of 5, a factor of over seven hundred thousand, and bcrypt cost 5 is far weaker than anyone should deploy. The card costs money: the RTX 5090 launched at a recommended 1,999 US dollars on the thirtieth of January 2025, which at the August 2026 exchange rate of about 1.3559 dollars to the pound is roughly 1,475 pounds of hardware. Divide the rates by that and you get the metric that lets you compare defences honestly, hashes per second per pound of hardware:

MD5:            about 149,600,000 H/s per pound
bcrypt cost 12: about        1.6  H/s per pound

Those two lines are the economic centre of the chapter. Per pound of the same graphics card, a bare MD5 store lets the attacker try roughly a hundred and fifty million guesses a second, and a bcrypt store at cost 12 lets him try under two. That ratio, close to a hundred million to one, is what a storage decision buys or throws away.

Renting is cheaper still and needs no capital. As of August 2026, an RTX 5090 rents on commodity GPU marketplaces from about 0.35 US dollars an hour on demand, and lower on interruptible spot capacity, which is about 0.26 pounds an hour. That turns cracking into a simple bill, and the bill for a fixed amount of guessing depends only on the hash:

To try 1,000,000,000,000 guesses Rented cost
MD5 under 0.1 pence
SHA-256 about 0.3 pence
bcrypt cost 12 about 30,000 pounds

One trillion guesses against an MD5 store costs a fraction of a penny and finishes in under five seconds on one rented card. The identical trillion guesses against a bcrypt cost-12 store costs about thirty thousand pounds and would take one card over thirteen years. Nothing changed but the stored algorithm. That is the entire argument of the storage chapter, expressed as an attacker’s invoice.

The third tier is custom silicon, and it needs an honest caveat that is usually left out. Application-specific integrated circuits, ASICs, are chips hard-wired for one calculation, and for Bitcoin’s double SHA-256 they are staggering: a 2024-era Bitmain Antminer S21 XP Hyd delivers about 473 trillion hashes a second at roughly 12 joules per trillion hashes, for around 1,000 US dollars, and the whole Bitcoin network as of August 2026 runs at about 914 exahashes a second, that is 914 followed by eighteen zeros of hashes every second. It is tempting to point that firehose at passwords. You mostly cannot. A Bitcoin ASIC computes one fixed function on one fixed input shape; it cannot run bcrypt, cannot run Argon2, cannot even run a bare SHA-256 over an arbitrary-length password the way a cracker needs. Custom silicon for password cracking does get built, as field-programmable gate arrays and occasionally purpose-made chips, and it beats graphics cards on fast and on lightly-iterated hashes. But its advantage collapses against a function that demands a large block of memory per guess, because memory is exactly what you cannot cheaply stamp thousands of copies of onto a chip. That collapse is the reason memory-hardness works, and we return to it after the rate tables.

Cracking rates for MD5, SHA-256, bcrypt and Argon2id#

Now put the four functions the storage chapter cares about side by side, as the rate at which a single modern graphics card grinds through guesses. Two honesties first. The MD5, SHA-256 and bcrypt figures are the RTX 5090 benchmark of February 2025; the bcrypt cost-12 figure is derived, not measured, and the derivation is shown below; the Argon2id figure is a separate measurement on an RTX 4090 in August 2025, because a directly comparable 5090 Argon2 benchmark at these parameters was not available to confirm, and mixing card generations is imperfect but does not disturb the orders of magnitude, which are the whole point.

The bcrypt derivation is simple arithmetic. Bcrypt’s cost parameter is a power of two: the number of internal rounds is 2 raised to the cost. The benchmark runs at cost 5, that is 2 to the 5th, or 32 rounds, at 304,800 hashes a second. Cost 12 is 2 to the 12th, or 4,096 rounds, which is 128 times as much work, so the rate falls to about 304,800 divided by 128, roughly 2,381 hashes a second per card. That matches the general shape of independently reported bcrypt cost-12 rates on this generation of hardware.

Function and setting Rate, one GPU
MD5 220.6 GH/s
SHA-256 28.35 GH/s
bcrypt cost 12 about 2,381 H/s
Argon2id 64 MiB, t=3 about 1,699 H/s

Look at the span. From MD5 to Argon2id at these settings is a factor of roughly 130 million in the attacker’s per-card throughput. A password store is a choice somewhere along that span, and the choice, not the password, sets the attacker’s speed. The Argon2id figure deserves a note of caution: Argon2 cracking rates on graphics cards vary sharply with the memory and parallelism settings and with the exact implementation, and independent measurements at heavier settings have reported rates from a few hundred down to tens of hashes a second per card, with one 2026 test of an eight-card RTX 5090 rig reporting about 490 hashes a second in total against Argon2id. The lesson is not a single magic number but a direction: every megabyte of required memory pushes the attacker’s rate down, and unlike raising the round count it pushes down his specialised hardware hardest.

The worked example, carried all the way#

Now spend Meera’s password against each of these. Recall her exposure: “Pihu@2019” matches the mask ?u?l?l?l with a literal “@” and ?d?d?d?d once the attacker fixes the symbol, a keyspace of 4,569,760,000 candidates, under five billion. On average he finds it after searching half of that, about 2,284,880,000 guesses, though against a targeted mask ordered by likely names and recent years he would very plausibly reach “Pihu@2019” far sooner; half the keyspace is a conservative, pessimistic-for-the-attacker estimate.

Give him a single serious box: eight RTX 5090 cards, roughly 12,000 pounds of hardware or about 2 pounds an hour rented. Multiply each per-card rate by eight and divide the expected 2.28 billion guesses by it.

Stored as Time to reach Pihu@2019
MD5 under 0.01 second
SHA-256 about 0.02 second
bcrypt cost 12 about 1.4 days
Argon2id 64 MiB about 2 days

Read this carefully, because it makes two points at once and they pull in opposite directions. First, storage matters enormously: the same password and the same rig give the attacker either an instant or a day and a half, a difference of roughly seven orders of magnitude, decided entirely by a line in the grocery service’s configuration. Against a database of eight million users, an attacker who can empty an MD5 store in an afternoon will absolutely do so and will get Meera; an attacker facing bcrypt or Argon2id will crack the weak majority and, for a merely-guessable password like Meera’s sitting a day and a half deep, will very often not bother, because there are easier accounts and his rig has a rent meter running. Good storage saved her, not by making her uncrackable, but by pricing her out of a mass operation.

Second, and this is the handover to the next chapter, good storage did not make “Pihu@2019” strong. A day and a half is not safety; it is a reprieve that depends on the attacker being busy and cheap. The reason it is only a day and a half, and not a millennium, is the password’s own shape: five billion candidates is nothing. Had Meera used four random words or a password-manager-generated string, the keyspace would have been so large that even MD5’s hundreds of billions a second would run for longer than the age of the universe, and bcrypt would be simply irrelevant. Storage strength and password strength defend against different attacks. The storage chapter bought Meera time; only the password-choice chapter can make that time infinite.

Why memory-hardness raises the attacker’s cost specifically#

We can now be exact about the claim from the plain half, that demanding memory attacks the attacker’s advantage rather than merely slowing him. The attacker’s advantage is throughput per pound, and it comes from parallelism: a graphics card is thousands of small arithmetic units running guesses side by side. A slow-but-small hash, bcrypt at a high cost, or PBKDF2 at many iterations, does nothing to that parallelism; it just makes each of the thousands of parallel guesses take longer, and the attacker answers by buying more cards or waiting. His cost per guess falls steadily as hardware improves, which is why iteration-only functions have to keep raising their counts every few years just to stand still.

A memory-hard function changes the binding constraint from arithmetic to memory. Argon2id at 64 mebibytes requires each single guess to fill and repeatedly read a 64-mebibyte block, and the security argument, set out in RFC 9106, “Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications”, published in September 2021, is that you cannot compute the function meaningfully faster by trading memory for computation; the memory is genuinely required. Now count what parallelism costs the attacker. A card with, say, 32 gigabytes of memory can hold at most about 500 blocks of 64 mebibytes at once, so it can run only about 500 guesses in parallel no matter how many thousands of arithmetic units it has; the units sit idle waiting for memory. The expensive silicon is throttled by a cheap resource. Custom chips fare no better, because on-chip memory is the most expensive thing to replicate, so the ASIC advantage that demolishes MD5 and dents bcrypt largely evaporates against Argon2. That is the precise sense in which memory-hardness raises the attacker’s cost: it does not just make each guess slower, it caps how many guesses the attacker’s best hardware can run at once, and it caps it low.

RFC 9106 gives two recommended settings worth knowing. Its first recommended option is Argon2id with 2 gibibytes of memory, one pass and four lanes; its second, for memory-constrained systems, is Argon2id with 64 mebibytes, three passes and four lanes. The OWASP Password Storage guidance, as of 2026, lists lighter balanced options such as 19 mebibytes with two passes, chosen so that a busy login server can afford the work while an attacker still cannot afford the parallelism. Both are the same idea: buy the defender a fraction of a second and charge the attacker a whole block of memory per guess.

The online attack and its limits#

Everything so far assumed a stolen database. The other attack is against the live login screen, and it is far weaker for the attacker and far more controllable for the defender, which is exactly why attackers avoid it when they can.

An online attack submits guesses through the normal authentication path: the attacker does not have the hashes, he simply tries to log in. Now every one of the defences the offline attacker escaped is back in force. Each guess costs a network round trip, tens of milliseconds at best, so the ceiling is thousands of guesses a second per connection rather than billions, before the defender does anything at all. And the defender can do a great deal, because unlike the offline case he is present and can count.

The two-shape trick from the previous chapters applies here. An attacker with one stolen username can try many passwords against it, which a per-account counter stops quickly. So the sophisticated online attacker inverts it: he tries one common password against a great many usernames, called password spraying, staying under any single account’s failure counter, and he spreads his guesses across thousands of network addresses so that no single source looks busy. Or he does not guess at all: he replays username-and-password pairs stolen from other breaches, called credential stuffing, which needs no guessing because the pair is already correct somewhere, and only tests how many other sites share it. Credential stuffing and its arithmetic belong to the shared-secret chapter, which sizes a real campaign; here it matters only as the reason online defences must watch for many accounts failing once, not just one account failing many times.

Rate limiting, lockout, and the denial-of-service trap#

The defender’s online tools are rate limiting and lockout, and both carry a trap that is the book’s recurring asymmetry in miniature.

Rate limiting, also called throttling, slows or blocks repeated attempts, usually with increasing delays. Lockout disables an account after a threshold of consecutive failures. Standards set the numbers. NIST Special Publication 800-63B, in its fourth revision published on the thirty-first of July 2025, requires in section 3.2.2 that a verifier limit consecutive failed authentication attempts on a single account to no more than 100 before disabling that authenticator, an upper bound agencies may tighten. The Payment Card Industry Data Security Standard, in version 4.0.1 current in 2026, is stricter for cardholder environments: requirement 8.3.4 mandates locking a user identifier after not more than 10 attempts and holding the lockout for a minimum of 30 minutes or until the user’s identity is confirmed.

Here is the trap. A hard lockout after a few failures turns the login form into a weapon against the honest user. If locking Meera’s account needs only ten wrong guesses, then anyone who knows her email can lock her out at will, ten deliberate failures at a time, all day, and she can never log in. The lockout meant to stop the attacker has become a denial-of-service vector against the victim: a way to deny service to the legitimate person. This is the same asymmetry the book keeps returning to. A system that wrongly admits an attacker produces a measured fraud number; a system that wrongly and repeatedly refuses the rightful owner produces silence and a person who cannot get in, and only the first is counted. A crude lockout optimises the counted harm and creates the uncounted one.

The modern resolution, reflected in the newer guidance, is to prefer soft, graduated friction over hard permanent lockout: exponential delays that cost an attacker dearly across many attempts but barely touch a human who mistypes twice; counting failures across source addresses and across the whole population to catch spraying; adding a proof-of-work or a challenge after a few failures rather than a wall; and reserving true account disabling for clear abuse, with a recovery path that does not itself become the weakest link. That recovery path is its own hazard and its own chapter later in the book. The online lesson for here is compact: rate limiting is necessary and nearly free, hard lockout is a blunt instrument that hands the attacker a way to harm the user, and the online channel is in any case the attacker’s second choice, which is why the offline defences, storage, are where the real money is spent.

Breached-password lists, and checking without leaking#

The last piece of ground turns the attacker’s own best weapon into a defence. The single most productive thing an offline attacker owns is a list of real passwords from prior breaches, sorted by frequency. The defender can own the same list and refuse any password on it, which is exactly what NIST SP 800-63B section 3.1.1.2 now requires: on setting or changing a password, the verifier shall compare it against a blocklist of commonly used, expected or compromised passwords, and reject a match. If a password has already appeared in breaches, it will be near the top of every attacker’s wordlist, so forbidding it removes the cheapest guesses from the board.

The obvious way to check is dangerous. If a website sends every new password to some central service to ask “has this leaked”, it has just handed the plaintext password to a third party, which is precisely the exposure the whole discipline exists to prevent. The elegant solution is k-anonymity, proposed for this use by Junade Ali of Cloudflare on the twenty-first of February 2018, alongside Troy Hunt’s Have I Been Pwned “Pwned Passwords” version 2, which then held about half a billion breached passwords. It lets you ask whether a password is in the list without revealing the password, or even the full hash of it, to the service.

The mechanism is worth doing exactly, because its cleverness and its limit are both in the detail. The client computes the SHA-1 hash of the candidate password, a 40-character hexadecimal string, and sends only the first five characters, the prefix, to the service. The service replies with every stored hash suffix that shares that prefix, each with a count of how often that password has been seen, and the client finishes the comparison locally. The service never learns which of the many passwords in that prefix bucket the client was asking about. A real exchange, for the password “P@ssw0rd”, whose SHA-1 begins 21BD1, run live in August 2026:

GET /range/21BD1 HTTP/1.1
Host: api.pwnedpasswords.com
Add-Padding: true

The response is a list of suffixes and counts, of which the client scans for the one matching the rest of its own hash:

...
2DC183F740EE76F27B78EB39C8AD972A757:6421042
0018A45C4D1DEF81644B54AB7F969B88D65:9
011053FD0102E94D6AE2F8B83D76FAF94F6:1
...

The client’s own SHA-1 of “P@ssw0rd” is 21BD1 followed by 2DC183...A757, so it finds that suffix in the list with a count of 6,421,042: this password has been seen over six million times across breaches, and should be refused. The service saw only the prefix 21BD1, a bucket shared by, in this live response, over nineteen hundred different password hashes, and could not tell which one the client held. The prevalence counts are real and current; a few, retrieved live in August 2026, show the range:

Password Times seen in corpus
password 52,372,427
P@ssw0rd 6,421,042
melobie 13
BDnd9102 1

Two refinements matter. First, the response size itself could leak information, because different prefixes return different numbers of suffixes, so a passive observer might fingerprint which bucket you asked for. The optional Add-Padding header, added on the fourth of March 2020, pads every response with fabricated zero-count entries to a random size, hiding the true count of real suffixes behind noise. A range query typically returns several hundred to a couple of thousand suffixes and, with padding, is deliberately smoothed. Second, and this is the honest limit that must not be skipped: k-anonymity hides which password in a bucket you asked about, but it does reveal the bucket, the prefix, and that is not nothing. The 2019 paper “Protocols for Checking Compromised Credentials”, by Lucy Li, Bijeeta Pal, Junade Ali, Nick Sullivan, Rahul Chatterjee and Thomas Ristenpart, showed that an attacker who observes the prefix a user queried, and knows the username, can raise his guessing success against that user substantially: in their measurements, knowing the hash prefix let an attacker compromise about 71 per cent of otherwise-uncompromised accounts within 1,000 guesses, roughly a twelvefold improvement over knowing nothing. The paper proposes stronger schemes, such as bucketing by the username’s hash rather than the password’s, that leak nothing about the password. The practical takeaway is that the prefix API is an excellent, privacy-respecting way to reject known-bad passwords at scale, and that its privacy is strong but not absolute, and you should not log the prefixes your users query.

15.98 Common wrong ideas#

Wrong: If passwords are hashed, a stolen database is safe. Right: Hashing only means the attacker must guess rather than read, and against a fast hash like MD5 or SHA-256 he guesses billions of times a second, recovering the weak majority in hours; safety depends on the hash being slow and salted, not merely on it being a hash.

Wrong: A salt is a secret that protects the password. Right: A salt is public, stored in the clear beside the hash, and unique per user; it defeats precomputed rainbow tables and stops one guess being tested against every account at once, but it does not slow a single guess against a single account and does nothing for a weak password.

Wrong: Rainbow tables are the modern way passwords get cracked. Right: Rainbow tables are a precomputation trick that any per-user salt defeats completely, so they are irrelevant against correctly salted storage; modern cracking is live guessing with dictionaries, rules and masks on graphics hardware.

Wrong: A strong, complex password cannot be cracked. Right: Complexity only sets the size of the keyspace; a nine-character password forced into the common capital-word-symbol-year shape is a keyspace of a few billion, reachable in about a day even under bcrypt, so strength is about unpredictability and length, not about ticking composition boxes.

Wrong: A slow hash such as bcrypt makes any password safe. Right: A slow hash multiplies the attacker’s cost per guess by a constant; if the password needs only billions of guesses, a large constant times billions is still an affordable bill, so slow storage buys time but cannot rescue a guessable password.

Wrong: More hashing iterations are always the best defence. Right: Iterations only make each guess slower without touching the attacker’s parallel-hardware advantage, so they must be raised every few years to keep pace; memory-hardness instead caps how many guesses the attacker’s hardware can run at once, which is why Argon2id at tens of megabytes resists custom silicon that shrugs off high iteration counts.

Wrong: Locking an account after a few failed logins is simply good security. Right: A hard lockout lets anyone who knows a username deny that person access at will by failing on purpose, turning the defence into a denial-of-service weapon against the honest user; graduated delays and population-wide monitoring stop attackers without punishing the victim.

Wrong: Bitcoin mining chips could crack any password instantly. Right: Those ASICs are hard-wired for one fixed calculation and cannot run bcrypt, Argon2 or even a general SHA-256 over a password, and their advantage collapses entirely against a memory-hard function, which starves custom silicon of the one resource it cannot cheaply replicate.

Wrong: Checking a password against a breach list means sending the password to someone. Right: The k-anonymity range API sends only the first five characters of the password’s SHA-1 hash, and the service returns a whole bucket of suffixes so it cannot tell which you asked about, letting you reject known-breached passwords without disclosing them.

Wrong: The k-anonymity check is perfectly private. Right: It hides which password in a bucket you queried but still reveals the five-character prefix, which a watching attacker who knows the username can exploit to improve guessing substantially, so the privacy is strong but not absolute and the queried prefixes should never be logged.

15.99 Chapter summary in 20 lines#

  1. After a database theft the attacker holds usernames and stored verifiers and computes offline, where no rate limit, lockout or logging can reach him.
  2. A modern verifier names its own algorithm and parameters in the clear, so the attacker learns exactly how much each guess will cost before he begins.
  3. The whole offline attack is one loop: guess a candidate, run it forward through the named function and salt, compare to the stored hash, repeat.
  4. Attackers guess in order of likelihood using four shapes: brute force, masks, breach-derived dictionaries and rule-based mangling, with dictionaries and rules by far the most productive.
  5. A mask exploits known structure, so Meera’s “Pihu@2019” collapses to a keyspace of about 4.57 billion once its capital-word-symbol-year shape is assumed.
  6. Rainbow tables trade memory for time by storing only the ends of long hash-and-reduce chains, and Oechslin cracked 99.9 per cent of alphanumeric Windows LM hashes in 13.6 seconds from 1.4 gigabytes in 2003.
  7. A per-user salt destroys rainbow tables completely, because a separate table would be needed for every possible salt value.
  8. Salt also stops one guess being tested against all accounts at once, but it never slows a single guess against a single account and never helps a weak password.
  9. Cracking runs on graphics processors, whose parallelism suits fast hashes; a single RTX 5090 in 2025 computes about 220 billion MD5 but only about 305 thousand bcrypt at cost 5.
  10. Per pound of the same card, an MD5 store allows roughly 150 million guesses a second and a bcrypt cost-12 store under two, a ratio close to a hundred million to one.
  11. One trillion guesses on a rented card costs under a penny against MD5 and about thirty thousand pounds against bcrypt cost 12; only the stored algorithm changed.
  12. Bitcoin ASICs are hard-wired for one calculation, cannot run password hashes, and their advantage vanishes against memory-hard functions.
  13. Across MD5, SHA-256, bcrypt cost 12 and Argon2id the attacker’s per-card rate spans a factor of over a hundred million, and that choice, not the password, sets his speed.
  14. Meera’s password falls in under a hundredth of a second under MD5 and in about a day and a half under bcrypt cost 12 on one eight-card rig, so good storage priced her out of a mass breach without making her uncrackable.
  15. Memory-hardness raises the attacker’s cost specifically by capping how many guesses his hardware can run at once, since each Argon2id guess needs its own large block of memory.
  16. RFC 9106 recommends Argon2id at 2 gibibytes with one pass, or 64 mebibytes with three passes, buying the defender a fraction of a second and charging the attacker a whole memory block per guess.
  17. Online attacks through the login form are throttled by network latency and by the defender’s counters, so attackers prefer spraying one password across many accounts or replaying stolen pairs.
  18. NIST SP 800-63B revision 4 of July 2025 caps consecutive failures at 100, while PCI DSS 4.0.1 requires lockout after at most 10 failures for at least 30 minutes.
  19. A hard lockout is a denial-of-service weapon against the honest user, so graduated delays and population-wide monitoring are preferred over blunt account disabling.
  20. Breached-password blocklists turn the attacker’s best wordlist into a defence, and k-anonymity lets a site check them by sending only a five-character hash prefix, though that prefix still leaks enough to help a targeted attacker.

Chapter sources: Martin Hellman, “A Cryptanalytic Time-Memory Trade-Off”, IEEE Transactions on Information Theory, volume 26, number 4, pages 401 to 406, 1980; Philippe Oechslin, “Making a Faster Cryptanalytic Time-Memory Trade-Off”, CRYPTO 2003, Springer LNCS volume 2729, pages 617 to 630; hashcat published benchmarks, RTX 5090 (hashcat v6.2.6-851, February 2025) and Argon2 mode 34000 on RTX 4090 (hashcat v7.0.0, August 2025), and the hashcat v7.0.0 release of 1 August 2025 with v7.1.2 of 23 August 2025; hashcat mask and rule-based attack documentation; RFC 9106, “Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications”, September 2021; OWASP Password Storage Cheat Sheet, parameters as of 2026; NIST Special Publication 800-63B revision 4, 31 July 2025, sections 3.1.1.2 and 3.2.2; PCI DSS version 4.0.1, requirement 8.3.4; Junade Ali, “Validating Leaked Passwords with k-Anonymity”, Cloudflare, 21 February 2018, and the Pwned Passwords padding announcement of 4 March 2020; Troy Hunt, “Pwned Passwords” version 2, February 2018, with live range-API figures retrieved August 2026; Lucy Li, Bijeeta Pal, Junade Ali, Nick Sullivan, Rahul Chatterjee and Thomas Ristenpart, “Protocols for Checking Compromised Credentials”, ACM CCS 2019; Jeremi Gosney’s 25-GPU cluster, Passwords^12, Oslo, December 2012; the 2015 Ashley Madison breach and the CynoSure Prime bcrypt-cost-12 analysis; Bitmain Antminer S21 XP Hyd specifications, November 2024, and Bitcoin network hashrate of August 2026.