Password Policies in Python — Core Concepts
Why password policies exist
Users choose terrible passwords. Studies consistently show that the most popular passwords are “123456”, “password”, and “qwerty.” Attackers maintain lists of billions of known passwords from breaches and run automated attacks against login forms. A password policy is your application’s defense against users inadvertently choosing passwords that an attacker would try first.
Modern vs. legacy policies
Traditional policies demanded complexity: uppercase, lowercase, digit, special character, minimum 8 characters. The result? Users created passwords like “Summer2024!” — technically compliant but utterly predictable.
NIST Special Publication 800-63B (2017, updated 2024) overturned the old advice:
Do require:
- Minimum 8 characters (15+ for privileged accounts)
- Checking against known breached password lists
- Checking against context-specific words (username, site name, email)
Don’t require:
- Mandatory complexity rules (uppercase, special characters)
- Regular password rotation (unless there’s evidence of compromise)
- Password hints or security questions
The reasoning: forced complexity leads to predictable patterns. Forced rotation leads to incrementing passwords (“Winter2024” → “Spring2024”). Both create a false sense of security.
Breach list checking
The most impactful policy check: is this password already known from a data breach? The “Have I Been Pwned” (HIBP) API uses a k-anonymity model — you send the first 5 characters of the password’s SHA-1 hash, and the API returns all hashes starting with that prefix. Your app checks locally whether the full hash appears in the response. The actual password never leaves your server.
import hashlib
import requests
def is_breached(password: str) -> bool:
sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
response = requests.get(f"https://api.pwnedpasswords.com/range/{prefix}")
return suffix in response.text
Strength estimation
Rather than binary pass/fail rules, modern libraries estimate password strength based on patterns, dictionary words, and keyboard sequences. The zxcvbn library (originally from Dropbox) provides a 0-4 score and estimated crack time:
from zxcvbn import zxcvbn
result = zxcvbn("correct horse battery staple")
# result["score"] = 4 (strongest)
# result["crack_times_display"]["offline_slow_hashing_1e4_per_second"] = "centuries"
result = zxcvbn("P@ssw0rd!")
# result["score"] = 0 (weakest)
# result["feedback"]["warning"] = "This is similar to a commonly used password"
Password hashing
Policy enforcement happens at input time. Storage is a separate concern: never store passwords in plaintext or with reversible encryption. Use a purpose-built hashing algorithm:
- bcrypt — the standard choice, built-in work factor
- argon2 — winner of the Password Hashing Competition, recommended for new projects
- scrypt — memory-hard, good alternative
All three are intentionally slow, making brute-force attacks against stolen hashes impractical.
Common misconception
“Longer maximum password limits improve security.” Setting a maximum password length of 20 or 30 characters can actually hurt. Passphrases like “correct horse battery staple” need 28 characters. NIST recommends supporting at least 64 characters. If your hashing algorithm handles it (bcrypt truncates at 72 bytes, but argon2 doesn’t), there’s little reason to cap length below 128 characters.
The one thing to remember: Modern password policies focus on length and breach list checking rather than complexity rules — enforcing “at least 12 characters and not from a known breach” does more than requiring uppercase and special characters ever did.
See Also
- Python Api Key Management Why apps use special passwords called API keys, and how to keep them safe — explained with a library card analogy
- Python Attribute Based Access Control How apps make fine-grained permission decisions based on who you are, what you're accessing, and the circumstances — explained with an airport analogy
- Python Audit Logging Learn Audit Logging with a clear mental model so your Python code is easier to trust and maintain.
- Python Bandit Security Scanning Why Bandit Security Scanning helps Python teams catch painful mistakes early without slowing daily development.
- Python Clickjacking Prevention How invisible website layers trick you into clicking the wrong thing, and how Python apps stop it