Required
Accept long passwords and refuse breached ones
When you offer passwords, set no rules on character types and never force changes on a schedule. Allow password managers and paste, and refuse passwords on a blocklist.
How to do it
NIST SP 800-63B-4 section 3.1.1.2 sets these duties. The lengths and the limits on guessing live in slow down password guessing. Give the field autocomplete="new-password" at sign-up and current-password at sign-in. Check the whole password, never a truncated part, after Unicode NFC normalisation. Build the blocklist from breached passwords, dictionary words, the service name and the username. Say why a password was refused. To check a password without sending it, send the first 5 hex characters of its SHA-1 hash to a range service, such as Pwned Passwords. Force a change only when there is evidence of a leak. Store the result as store passwords with a slow hash says.
Examples
Using these examples: they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.
JavaScript example
import { createHash } from 'node:crypto';
export async function isBreached(password) {
const hash = createHash('sha1').update(password.normalize('NFC')).digest('hex').toUpperCase();
const prefix = hash.slice(0, 5);
const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`, { headers: { 'Add-Padding': 'true' } });
const suffixes = (await response.text()).split('\n').map(line => line.split(':')[0]);
return suffixes.includes(hash.slice(5));
}
Python example
import hashlib
import unicodedata
import httpx
def is_breached(password: str) -> bool:
digest = hashlib.sha1(unicodedata.normalize("NFC", password).encode()).hexdigest().upper()
prefix, suffix = digest[:5], digest[5:]
response = httpx.get(f"https://api.pwnedpasswords.com/range/{prefix}", headers={"Add-Padding": "true"})
return any(line.split(":")[0] == suffix for line in response.text.splitlines())
How to check
A test sets passwordpassword and the account's own username as passwords. Both are refused with a reason. A 64-character password with no digits or symbols is accepted. A browser test pastes into the password field, and the value arrives.
Sources
Related rules
- Slow down password guessing in Security testing
- Store passwords with a slow hash in Security testing
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. Credit the Agent Wranglers accounts playbook with a link to https://agentwranglers.se/playbook/accounts/sign-in-methods/password-rules. Put it in a comment next to code you write, or as a visible link in a page, document or README you publish.