Skip to content

Chapter 5 of 204 min readBy Deepak GuptaPublished

Password Policy and Breached-Password Screening

Your password policy should be short: a length rule, a blocklist, and nothing else. NIST SP 800-63B Revision 4 says verifiers shall not impose composition rules or periodic changes, and shall reject passwords found on a blocklist of commonly used, expected, or compromised values. (NIST SP 800-63B-4, Sec. 3.1.1)

The policy to implement

Rule Implementation
Minimum length 15 characters when the password is the only factor; 8 when it is always paired with MFA (NIST)
Maximum length Accept at least 64 characters
Characters Accept all printable ASCII, spaces, and Unicode; normalize before hashing
Composition rules None
Scheduled expiry None; force a change only on evidence of compromise
Blocklist Breached passwords, common passwords, and context words (your brand, the username)
Hints and security questions None
Paste and password managers Always allowed
Show password Offer a toggle

If you also fall under PCI DSS 4.0 for systems in the cardholder data environment, its requirement 8.3.6 sets a 12-character minimum. Organizations that go fully passwordless are no longer required to meet the password-specific requirements. (Cloud Security Alliance) Pick the strictest rule that applies and write down why.

Breached-password screening, step by step

The standard approach is the Have I Been Pwned Pwned Passwords range API, which uses k-anonymity so the password never leaves your server. (Troy Hunt)

  1. Compute the SHA-1 of the candidate password (only for this lookup; never store it).
  2. Send the first 5 hex characters to https://api.pwnedpasswords.com/range/{prefix}, with the Add-Padding: true header so response size does not leak information.
  3. The API returns every hash suffix that shares the prefix, with a breach count.
  4. Compare suffixes locally. If yours appears, reject the password with a helpful message.
import hashlib, requests

def is_breached(password: str) -> bool:
    h = hashlib.sha1(password.encode("utf-8")).hexdigest().upper()
    prefix, suffix = h[:5], h[5:]
    r = requests.get(f"https://api.pwnedpasswords.com/range/{prefix}",
                     headers={"Add-Padding": "true"}, timeout=2)
    r.raise_for_status()
    for line in r.text.splitlines():
        s, count = line.split(":")
        if s == suffix and int(count) > 0:   # padded rows have count 0
            return True
    return False

For high volume or air-gapped environments, download the full dataset and host the range lookup yourself. Decide your failure mode in advance: if the lookup times out at signup, fail open with a background re-check rather than blocking signups.

When to check

  • At signup and password change: reject breached passwords.
  • At sign-in: check after successful verification. If breached, let the user in but require a change, ideally offering a passkey instead.
  • Continuously: when new breach corpora are published, the only way to recheck stored hashes is at next sign-in. Do not try to crack your own users' hashes.

Failed-attempt limits

NIST requires limiting consecutive failed attempts with one authenticator on a single account to no more than 100. (NIST) In practice, use progressive delays per account and per IP, risk-based challenges, and notifications, instead of hard lockouts that attackers can use to deny service to real users (Chapter 13).

Error messages

Tell users what to do, not what rule they broke in red: "This password has appeared in a data breach. Choose a different one, or use a passkey instead."

Warning

Common mistakes

  • Keeping complexity rules because an old audit checklist asked for them.
  • Truncating long passwords silently.
  • Sending full password hashes to any third-party API.

Key takeaways

  • The policy is a length rule and a blocklist: 15 characters single-factor, 8 with MFA, no composition rules, no scheduled expiry.
  • Screen passwords with the Pwned Passwords range API; the password never leaves your server.
  • Check at signup, password change, and after each successful sign-in.
  • Use progressive delays, not hard lockouts, and stay well under NIST's ceiling of 100 failures.