How Breached Password Checks Work (k-Anonymity Explained)
Checking a password against known data breaches is one of the most effective security measures there is, and it is now required by NIST for any system that sets passwords. But the obvious way to do it, sending the password to a service and asking “have you seen this?”, would be a terrible idea. This guide explains how breach checks avoid that problem with a technique called k-anonymity, what the result does and doesn’t tell you, and how to add the same check to your own sign-up form.
Why breached passwords matter more than complexity
Attackers don’t guess passwords at random. Their first move, both against login forms and against stolen hash databases, is to try passwords that real people have already used, taken from previous breaches. Two attacks rely on these lists:
- Credential stuffing: leaked email and password pairs are replayed automatically against other websites. It works because people reuse passwords.
- Dictionary cracking: leaked passwords, sorted by popularity, are hashed and compared first. A password that appears in a breach list is cracked almost immediately, however long it is.
This is why P@ssw0rd123!, which satisfies almost every complexity rule, is a terrible password: it appears in breach data tens of thousands of times.
The Pwned Passwords dataset
Have I Been Pwned, run by security researcher Troy Hunt, publishes Pwned Passwords: more than 900 million unique passwords recovered from breaches, each with the number of times it was seen. The data is available as a free API and as a downloadable file. Password managers, identity providers and many large sites query it.
k-anonymity: checking without revealing
The API never receives your password, or even its full hash. Here is the exact procedure our breached password checker follows:
- Your browser computes the SHA-1 hash of the password.
passwordbecomes5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8. - It sends only the first five hex characters,
5BAA6, toapi.pwnedpasswords.com/range/5BAA6. - The API returns every breached hash that starts with that prefix, without the prefix, each with a count: typically 800 to 1,000 lines.
- Your browser looks for the remaining 35 characters of your hash in that list. If it is there, the count says how often the password has been seen.
Five hex characters give 165 = 1,048,576 possible prefixes, so every prefix is shared by roughly a million possible passwords in the dataset’s space and by hundreds of real breached ones. The service learns only that your password is one of them. This is k-anonymity: your query is indistinguishable from those of everyone else whose password shares the prefix.
Padding: hiding the response size
Because prefixes return different numbers of results, someone watching encrypted traffic could in theory narrow down which prefix was requested from the response size. Sending the Add-Padding: true header makes the API add random fake entries with a count of zero, so all responses are similar in size. Our checker always sends it and ignores the zero-count rows.
Isn’t SHA-1 broken?
SHA-1 is broken for collision resistance: researchers can craft two different files with the same hash. That matters for digital signatures, not here. The hash is only used as a lookup key, and only a 20-bit prefix is ever disclosed. The real protection is that five characters reveal almost nothing about the password.
What a result means
- Found: the password is known to attackers. Stop using it everywhere, starting with your email account, and replace it with a unique random password.
- Not found: the password has not appeared in a breach in the dataset. It may still be weak (
Xk7is not in any list and falls instantly). Check its length with the crack time calculator. - The count: a password seen once may be someone’s unique password leaked in one breach; one seen a million times is a common choice. Either way, don’t use it.
Adding breach checks to your own application
NIST SP 800-63B-4 requires verifiers to compare new passwords against a blocklist that includes passwords from previous breaches, and to reject matches. The range API makes this easy:
- Run the check when a user sets or changes a password, server-side or in the browser, and explain the rejection (“this password has appeared in a data breach”).
- Decide what to do if the API is unreachable: most systems allow the password and retry later rather than blocking sign-ups.
- For high volume or offline use, download the full hash list and query it locally.
- Consider a threshold: rejecting anything seen even once is the safest choice and is what NIST’s wording implies.
Our password policy generator includes a breached-password rule, and the NIST password guidelines guide covers the rest of a modern policy.
Key takeaways
- Breached passwords are tried first by every attacker, so checking against breach data matters more than complexity rules.
- k-anonymity lets you check without sending the password: only 5 characters of its SHA-1 hash leave your device.
- “Not found” is necessary but not sufficient: a password must also be long and random.