Learn

Credential exposure

What a Breach Exposes: Plaintext to Argon2id

Breach notifications are written to be read quickly and understood vaguely. Hashed, salted, encrypted and protected do very different amounts of work, and one of them is not a technical term at all. Two things are worth knowing before you decode one. NIST SP 800-63B Revision 4 names no iteration count and no specific algorithm, delegating instead to SP 800-132, whose only figure has read 1,000 iterations since December 2010, against OWASP's 600,000 for PBKDF2 with SHA-256. And the live standards disagree on Argon2id memory by about a hundredfold, because RFC 9106's first recommended option is 2 GiB while OWASP's summary recommendation is 19 MiB. Both are current.

A breach notification arrives and says the passwords were hashed. Somebody senior asks whether that is good news. The honest answer is that it depends on four things the notification almost certainly does not mention.

This is how to read one, and where the standards themselves disagree.

The words, in ascending order of actual protection

Plaintext means the password was stored as typed. There is nothing to break. Every password in the file is immediately usable anywhere it was reused.

Encrypted is the word that should make you ask a question. Encryption is reversible by design, which means a key exists somewhere, and in a breach where the database was taken the key is frequently taken with it. Passwords should not be encrypted, they should be hashed. A notification using this word is either being imprecise or describing a design mistake.

Hashed on its own says very little. It means a one-way function was applied. It does not say which function, how many times, or whether a salt was used, and those three decide whether the hash is a speed bump or a wall.

Hashed and salted means a random value was mixed in per password before hashing, so identical passwords produce different hashes. This defeats precomputed lookup tables, which is real but narrow: it stops an attacker reusing work across accounts, and does nothing to stop them attacking one hash at a time.

Hashed with a purpose-built password hashing function is the only phrase in a notification that should genuinely reassure you. Argon2id, scrypt, bcrypt and PBKDF2 exist to be deliberately slow and, in the first two cases, deliberately memory-hungry. A fast general-purpose hash such as MD5 or SHA-1 was designed to be quick, which is exactly the wrong property here.

Protected is not a technical term. It appears in notifications precisely because it commits to nothing.

What the current standard actually requires

This is the part that surprises people, including engineers.

NIST SP 800-63B Revision 4, in section 3.1.1.2, requires that passwords shall be salted and hashed using a suitable password hashing scheme, and that the salt shall be at least 32 bits. It requires that where a keyed hash is used, the secret key shall be stored separately from the hashed passwords.

What it does not do is name an algorithm or an iteration count. On cost it says only that the chosen cost factor should be as high as practical without negatively impacting verifier performance. And on the scheme itself it delegates: an approved password hashing scheme published in the latest revision of SP 800-132, or updated NIST guidelines, should be used. A should, not a shall.

So the binding requirements are salting, hashing, a 32-bit salt floor and separate storage of the keyed hash secret. Everything about strength is a recommendation pointing somewhere else.

Where it points, and why that is a problem

SP 800-132 was published in December 2010 and has not been revised. Its section 5.2 states that a minimum iteration count of 1,000 is recommended. Its section 5.1 requires that the randomly generated portion of the salt shall be at least 128 bits.

Two consequences follow, and both are odd.

NIST's only live iteration figure is 1,000, set in 2010. OWASP's current recommendation for PBKDF2 with SHA-256 is 600,000. That is a factor of 600 between the number a compliance auditor can cite to NIST and the number a practitioner would actually use. One caveat on the comparison: SP 800-132 is titled for storage applications and governs key derivation rather than verifier storage, so applying its count to password hashing is an extension by way of the cross-reference in SP 800-63B.

The newer document has the looser salt requirement. SP 800-63B Revision 4 requires at least 32 bits of salt. SP 800-132 requires at least 128 bits. The 2025 revision is four times more permissive than the 2010 document it tells you to follow.

The numbers a practitioner would actually use

These come from OWASP's Password Storage Cheat Sheet, retrieved 26 October 2026. The rendered page carries no revision date, which is worth knowing when citing it.

Argon2id, the default recommendation: a minimum of m=19456, meaning 19 MiB of memory, with t=2 iterations and p=1 lane. OWASP also lists equivalent configurations trading memory against iterations, including m=47104 with t=1, m=12288 with t=3, m=9216 with t=4 and m=7168 with t=5.

scrypt: a minimum CPU and memory cost of N=2^17, which is 128 MiB, with block size r=8 and p=1. Equivalents run from N=2^16 with p=2 down to N=2^13 with p=10.

PBKDF2: 600,000 iterations with HMAC-SHA-256, or 220,000 with HMAC-SHA-512. SHA-1 is listed at 1,400,000 and marked legacy only.

bcrypt: a work factor of at least 10, as high as verification performance allows. Note its 72-byte input limit, which silently truncates longer passwords.

The hundredfold disagreement nobody mentions

RFC 9106, published September 2021, is the Argon2 specification. Its section 4 gives a first recommended option of Argon2id with t=1 iteration, p=4 lanes and m=2^21, which the RFC states is 2 GiB of RAM, with a 128-bit salt and 256-bit tag. Its second recommended option is t=3, p=4 and m=2^16, which is 64 MiB.

Against OWASP's 19 MiB summary recommendation, the RFC's first option is roughly 108 times more memory and its second roughly 3.4 times. Both documents are current and neither is wrong. The comparison is memory only and not like for like, since the RFC uses four lanes against OWASP's one and the iteration counts differ.

The practical reading: there is no single correct answer, the spectrum spans two orders of magnitude, and anyone who tells you the parameters are settled has read one document.

When the format stops mattering

Here is the part that should change how you read a notification.

A reused password is exposed regardless of the algorithm. If the same password protects a work account, the hash strength at the breached service is irrelevant to that account, because the attacker does not need to crack anything. They have the plaintext from somewhere else, or the user will hand it over by reusing it again.

A weak password falls to any algorithm. Argon2id at 2 GiB does not save a password that appears in a wordlist, it only makes the attacker wait slightly longer before trying it.

A stealer log contains the plaintext. Infostealer malware takes what the browser saved, which is the password itself, not a hash of it. No storage decision at any service protects against that, because the theft happens on the user's machine before anything is hashed. For a large share of real credential exposure, the hashing question simply does not arise.

And a session cookie bypasses the password entirely. A stolen session can be resumed without the password and without the second factor.

What to do when a notification arrives

Read what the format actually commits to, using the ladder above, and treat "protected" and "encrypted" as prompts for a question rather than answers.

Assume reuse. The exposed password's value to an attacker is mostly about where else it was used, which is a question about your users rather than about the breached company.

Check the exposed value against your own directory, without handling plaintext yourself. NIST SP 800-63B Revision 4 section 3.1.1.2 requires verifiers to compare a prospective password against a blocklist of known commonly used, expected or compromised values, so the same capability serves both purposes.

Change on evidence, not on a schedule. The same section states that verifiers and credential service providers shall not require subscribers to change passwords periodically, and that verifiers shall force a change where there is evidence the authenticator has been compromised.

Revoke sessions explicitly. In most identity providers a password change does not end existing sessions, and revocation is a separate action.

Check your own exposure

SphereTI monitors credential exposure across breach corpora, infostealer logs and credential lists, and reports which of your domains appear, where the credential came from and when it surfaced. Request a free exposure report. No password is needed.

Common questions

The notification says our passwords were hashed. Are we safe?

Not knowably, because hashed on its own names no algorithm, no cost factor and no salt. An unsalted MD5 hash of a common password is recovered instantly; an Argon2id hash at sensible parameters is not. Ask which function, with what parameters, and whether a per-password salt was used. If the notification does not say, treat the exposure as real until told otherwise, and assume reuse is the larger problem either way.

Which algorithm and parameters should we actually use?

Take the numbers from OWASP or RFC 9106, because the only live NIST figure dates from December 2010 and is 1,000 PBKDF2 iterations. OWASP gives Argon2id at a minimum of m=19456, meaning 19 MiB, with t=2 and p=1. For scrypt it gives N=2^17, meaning 128 MiB, with r=8 and p=1. For PBKDF2 it gives 600,000 iterations with HMAC-SHA-256 and 220,000 with HMAC-SHA-512. For bcrypt it gives a work factor of at least 10, while positioning bcrypt for legacy systems where newer algorithms are unavailable.

A notification says the data was encrypted. How should I read that?

As a reason to ask a question. Encryption is reversible and therefore requires a key, and in a breach where the database was exfiltrated the key is frequently exfiltrated too. Passwords should be hashed rather than encrypted. The word may be imprecise drafting, or it may be describing exactly what happened, and the difference matters.

Why do NIST and OWASP disagree so much?

Because they are doing different jobs. SP 800-63B Revision 4 sets binding requirements that must hold across every federal system, so it specifies salting, hashing, a 32-bit salt floor and separate key storage, then delegates strength to a recommendation. OWASP publishes practitioner guidance that can be updated as hardware changes. The gap shows up most clearly in the fact that NIST's only live iteration figure, 1,000, was set in December 2010 and OWASP's is 600,000.

Does strong hashing protect against infostealers?

No, and this is the most important limitation in the article. Infostealer malware takes the password from the browser on the user's own machine, before any service hashes anything. The stealer log contains plaintext. No storage decision anywhere protects against it, which is why a large share of real credential exposure has nothing to do with how any company stored anything.

Should we force a reset across the business after a third-party breach?

Only for accounts where there is evidence of exposure, which is what the current standard requires. NIST SP 800-63B Revision 4 section 3.1.1.2 prohibits periodic rotation and requires a forced change where there is evidence the authenticator has been compromised. A blanket reset also tends to skip the blocklist check the same section requires, and does nothing about sessions already issued, which survive a password change in most identity providers.

Check your own exposure

Enter a work email and SphereTI checks it against known breaches and stealer logs, then shows you which credentials have been exposed. Free, and it never asks for your password.

Get your free report

Keep reading