open atlas
↑ Back to track
Offensive Security RED · 03 · 03

Password attacks and cracking

Offline cracking is an economics problem: a fast unsalted hash turns a stolen dump into a free wordlist, while a slow salted hash makes the same dump worthless. See the guesses-per-second math behind why work factor and per-user salt decide who wins.

RED Middle ◷ 15 min
Level
FoundationsJuniorMiddleSenior

On an authorized engagement, a tester exfiltrates a users table from a lab database and stares at a column of password hashes. Nothing is decrypted yet — a hash is one-way, there is no key to steal. And yet, within the hour, a third of the accounts have plaintext passwords next to them. Not because the tester broke the math, but because they bought a rented GPU rig and the application had stored those passwords as raw SHA-256. On that hardware a single card guesses billions of SHA-256 candidates per second; the entire RockYou wordlist of fourteen million leaked passwords runs out in a fraction of a second, and a brute force over every 8-character lowercase string finishes before lunch. The defender’s password policy never mattered after the dump leaked. What mattered was a single design choice made years earlier: which function turned the password into that hash, and whether it was salted. This lesson is the autopsy of that choice.

By the end of this lesson you’ll be able to reason about offline cracking as an economics problem — guesses per second times hash cost — and explain why a slow, salted hash is the control that decides whether a stolen dump is catastrophic or worthless.

Authorization first: this is a defender’s autopsy

Everything below assumes a signed engagement with explicit scope — a deliberately vulnerable lab, a CTF, a hash dump from your own system, or a target inside a bug-bounty program. Running a cracker against credentials you were not authorized to obtain is unauthorized access in most jurisdictions, and “I only wanted to see how weak they were” is not a defense. We study the attacker’s economics for one reason: you cannot price a control you don’t understand. The whole question “is bcrypt enough?” is unanswerable until you can estimate how many guesses per second your hash costs the attacker and how many they need. Read this as the autopsy a defender runs on their own credential store — learning to see the hash column the way a cracker does, so the work factor is right before the dump ever leaks. No copy-paste cracking recipes here, only the mechanism and its numbers.

Online vs offline: two completely different games

There are two arenas, and they are not close in difficulty. An online attack guesses against the live system — submitting passwords to a login form, an API, an SSH daemon. Here the defender controls the clock: rate limiting, account lockout, exponential backoff, and CAPTCHAs cap the attacker at maybe a few guesses per second per account, and every guess is visible in the logs. Online guessing is slow and noisy by design, which is why the dominant online attack today isn’t blind guessing at all — it’s credential stuffing, replaying username-password pairs already leaked from other breaches, betting on reuse. That bet pays because people reuse passwords; the defense is MFA and breached-password screening, not a faster hash.

An offline attack is the other game entirely. Once the attacker holds the hashes — from a SQL injection dump, a stolen backup, a compromised replica — there is no login form between them and the answer. They compute candidate hashes on their own hardware, as fast as their silicon allows, with no rate limit, no lockout, and no log entry on your side. The defender has lost the clock; the only remaining lever is how expensive each single hash computation is. That is the pivot of this entire lesson: online security is about rate; offline security is about per-hash cost. A 12-character password behind a fast hash is trivially crackable offline and perfectly safe online — same password, opposite outcomes, because the games have different physics.

The economics: guesses per second times hash cost

Offline cracking reduces to one inequality the attacker runs in their head: can I afford enough guesses to cover the likely keyspace before the data stops being worth it? The two terms are the rate (how many candidate hashes per second their hardware computes) and the keyspace (how many candidates they must try). The defender can’t shrink the keyspace — that’s the user’s password — so the entire defensive game is collapsing the rate, and the rate is set almost entirely by which hash function you chose.

The numbers are brutal and worth internalizing. A modern GPU computes on the order of tens of billions of fast-hash guesses per second for a single MD5 or SHA-256 — and a rented multi-GPU rig multiplies that. Against that rate, a fast hash is no protection at all: the entire RockYou list clears in well under a second, and exhaustive brute force chews through short keyspaces in minutes. The defense is to pick a deliberately slow password hash — bcrypt, scrypt, Argon2 — built to be expensive on purpose. Against bcrypt at a realistic cost factor, the same GPU drops from tens of billions to a few tens of thousands of guesses per second: a slowdown of roughly a million times. A wordlist that cleared in under a second now takes hours; an exhaustive search that finished before lunch now outlives the data’s value. Nothing about the password changed — only the per-hash cost did, and that one factor moved the attack from trivial to uneconomic.

Hash functionBuilt forGPU guesses/sec (order of magnitude)Offline verdict
MD5 / SHA-1 / SHA-256Speed (file integrity, not passwords)~1010–1011Dump ≈ free wordlist; do not use for passwords
bcrypt (cost 12)Slowness, tunable work factor~104~106× slower; wordlist now takes hours
scrypt / Argon2idSlowness + memory-hardness~103–104Also defeats cheap GPU/ASIC parallelism via RAM cost

Salt: why a per-user random value kills precomputation

Work factor slows down one hash; salt breaks the attacker’s ability to amortize work across many hashes. A salt is a unique random value stored alongside each hash and mixed into the input before hashing, so two users with the identical password Summer2024! end up with two completely different hashes. That single property destroys three attacker economies at once. First, it kills rainbow tables — gigantic precomputed lookup tables that map hashes back to plaintext: a rainbow table is built for unsalted hashes, and a per-user salt means the attacker would need a separate table per salt, which is computationally absurd, so precomputation collapses back into live brute force. Second, it kills hash deduplication: in an unsalted dump, every account using the world’s most common password shares one hash, so cracking it once cracks them all; salt forces the attacker to attack each hash individually. Third, it removes the at-a-glance signal that two users share a password.

The crucial nuance for a senior: salt is not secret and is not meant to be. It’s stored in plaintext right next to the hash, and that’s fine — its job is uniqueness, not confidentiality. Salt does nothing to slow down cracking a single targeted hash; if the attacker wants one specific user’s password, salt doesn’t help the defender at all. Salt’s entire value is at scale: it converts “crack the dump once, unlock everyone” into “crack every account from scratch, one at a time,” which combined with a slow work factor is what makes a full-database dump economically hopeless. Modern password hashes (bcrypt, Argon2) generate and embed the salt for you, which is exactly why you must never hand-roll this with a bare fast hash.

Why this works

Why is GPU hardware so devastating against fast hashes specifically, and why does Argon2 add memory-hardness on top of slowness? A fast hash like SHA-256 is tiny, stateless arithmetic — exactly the workload a GPU’s thousands of small cores run in massive parallel, and an ASIC bakes into silicon even cheaper. Pure slowness (bcrypt’s cost factor) helps, but a richer attacker can still throw more cores at the problem. Memory-hardness changes the economics again: Argon2id and scrypt force each hash to allocate a large block of RAM, and RAM is expensive to replicate across thousands of parallel cores or to put on an ASIC. So memory cost caps how much an attacker can parallelize, blunting the GPU/ASIC advantage that pure-compute slowness leaves open. That’s why current guidance reaches for Argon2id first, with bcrypt as the well-understood fallback.

How a defender prices the control

Put the pieces together and the defender’s job is to make offline cracking cost more than the data is worth. That means three decisions, in order. Choose a slow, memory-hard password hash (Argon2id, or bcrypt/scrypt) — never a bare fast hash, never encryption (encryption is reversible; you want one-way). Tune the work factor to the highest cost your login latency budget tolerates — roughly aiming for a fraction of a second per hash on your own hardware — and revisit it as hardware gets faster, because a cost factor chosen in 2015 is too cheap today. Rely on the library’s automatic per-user salt so a single crack never generalizes across the database. Then accept that hashing is the last line, not the only one: cap the online game with rate limiting, lockout, and MFA, and screen new passwords against known-breached lists so credential stuffing has nothing to replay. The recurring senior reflex is the same as everywhere else in security — assume the dump will eventually leak, and make sure the control is already priced so that when it does, the attacker’s arithmetic doesn’t close.

Pick the best fit

You're hardening a credential store before launch. The current design stores passwords as raw SHA-256. On an authorized review you confirm a single GPU would crack most of a leaked dump in under an hour. Pick the change that actually fixes the offline-cracking exposure.

Quiz

The same 12-character password is perfectly safe against an online attack on the login form but trivially cracked once the hash dump leaks (assuming a fast hash). Why the opposite outcomes?

Quiz

A reviewer says 'we added a per-user salt, so our SHA-256 password store is now safe against offline cracking.' What's wrong with that claim?

Order the steps

Order the steps of an offline cracking campaign against a fast unsalted hash dump, from acquisition to result:

  1. 1 Attacker obtains the hash dump (SQLi, stolen backup, compromised replica)
  2. 2 Identifies the hash function and confirms it's fast and unsalted
  3. 3 Runs a wordlist (e.g. RockYou) of common/leaked passwords on GPU hardware
  4. 4 Falls back to masked brute force over the remaining short keyspace
  5. 5 Recovers plaintext for a large fraction of accounts, then tries them elsewhere (stuffing)
Recall before you leave
  1. 01
    Explain why online and offline password attacks are completely different games, and what single lever the defender has against offline cracking.
  2. 02
    Walk through the offline cracking economics and explain how work factor and salt each change the attacker's arithmetic — and why one without the other is insufficient.
Recap

Password cracking is an economics problem, and the senior insight is that the defender’s only offline lever is per-hash cost. Online and offline are different games with different physics: online, the defender controls the clock with rate limiting, lockout, and MFA, so the live threat is credential stuffing (reused pairs from other breaches), not blind guessing; offline, the attacker holds the dump and computes guesses on their own hardware with no rate limit, no lockout, and no logs. Against a fast unsalted hash (MD5/SHA-256) a single GPU runs tens of billions of guesses per second, so the dump is effectively a free wordlist — the whole RockYou list clears in under a second. The fix is two orthogonal levers: a deliberately slow, memory-hard hash (Argon2id, bcrypt, scrypt) that cuts the attacker’s rate by ~10^6, plus an automatic per-user salt that kills rainbow tables and cross-account dedup so one crack never generalizes. Neither alone suffices — salt without slowness leaves each hash brute-forceable, slowness without salt re-enables precomputation — and length policy or a leakable pepper are not substitutes. Now when you read a credential store, your first question is the same: how many guesses per second does this hash cost an attacker who already holds the dump?

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 6 done
Connected lessons

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.