Python · Password breach checks
Check breached passwords in Python at signup
NIST SP 800-63B recommends rejecting passwords that appear in lists of previously breached passwords. In Python the check fits neatly inside a form validator: post the candidate password, read breached and count, and raise a validation error if it has been seen before. Finowl hashes the password with SHA-1 and only uses a 5-character prefix for its range search, and it never logs or stores the value.
The example reads the password with getpass so it never echoes to the terminal or lands in shell history. Error messages mention status codes and error codes only, never the password itself, which matters once exceptions start flowing into Sentry or your log aggregator. The same function slots into a Django validator class or a FastAPI dependency with no changes.
Steps
- 1
Install requests and export FINOWL_API_KEY in the environment that runs your app.
- 2
Copy breached_count into the module that validates new and changed passwords.
- 3
Call it after your length and character rules pass, so obviously invalid input never leaves the server.
- 4
Reject the password with a clear message when count is greater than zero.
- 5
Decide in advance whether signups proceed or block if Finowl is unreachable.
pip install requestsimport getpass
import os
import time
import requests
API_URL = "https://api.finowl.co/v1/breaches/password"
HEADERS = {
"Authorization": f"Bearer {os.environ['FINOWL_API_KEY']}",
"Content-Type": "application/json",
}
def breached_count(password, attempts=3):
"""Return how many times a password appears in breach data.
The password is sent only in the request body and is never logged here.
"""
for _ in range(attempts):
resp = requests.post(API_URL, json={"password": password}, headers=HEADERS, timeout=10)
if resp.status_code == 200:
data = resp.json()
return data["count"] if data["breached"] else 0
try:
code = resp.json()["error"]["code"]
except (ValueError, KeyError, TypeError):
code = "unknown_error"
if resp.status_code == 400:
raise ValueError(f"Password failed validation ({code})")
if resp.status_code == 401:
raise PermissionError("Unauthorized: check FINOWL_API_KEY")
if resp.status_code == 429 and code == "rate_limited":
time.sleep(int(resp.headers.get("Retry-After", "1")))
continue
raise RuntimeError(f"Password check failed with {resp.status_code}: {code}")
raise RuntimeError("Password check still rate limited")
if __name__ == "__main__":
candidate = getpass.getpass("Password to check: ")
count = breached_count(candidate)
if count:
print(f"Seen {count:,} times in breach data. Pick a different password.")
else:
print("Not found in breach data.")IN PRODUCTION
Production tips
Configure your logging so request bodies are never captured. Libraries like urllib3 debug logging and some APM agents can record payloads, so disable body capture for this endpoint explicitly.
Pick a fail-open or fail-closed policy for timeouts. Many teams let the signup proceed and queue a recheck, rather than blocking a real user because of a network blip.
Show users a helpful message, for example that the password has appeared in known breaches, instead of the raw count. The count is useful for internal thresholds, not for the UI.
Each lookup costs 1 credit, with 100 free credits every month. See pricing and error codes.
A FEW GOOD QUESTIONS
Common questions
Related guides
- Check email breaches in Python with requests
- Predict gender from names in Python
- Screen breached passwords in Node.js
- Check breached passwords in TypeScript
- Check breached passwords in Go before storing them
- Check breached passwords in Java at registration
- Breached password checks for fintech accounts
- Breached password screening for e-commerce accounts
- Breached password checks for SaaS signup and resets
Check breached passwords in Python at signup
Grab an API key, paste the example and run it. 100 free credits every month.