In 2024, a breach exposed 26 billion records. Your email is probably in at least one breach.
Have I Been Pwned (HIBP) lets you check — and their password API is completely free. No key needed.
The APIs
HIBP has two types of access:
- Password API — Free, no key, unlimited
- Email breach API — Requires paid key ($3.50/month) or use the free website
Let's focus on what's free.
1. Check Passwords Without Sending Them (k-Anonymity)
This is brilliant: you send only the first 5 characters of the SHA-1 hash. HIBP returns all matching hashes. Your password never leaves your machine.
import hashlib
import requests
def check_password(password):
"""Check if a password has been in a data breach.
Uses k-anonymity: only first 5 chars of hash are sent."""
sha1 = hashlib.sha1(password.encode('utf-8')).hexdigest().upper()
prefix = sha1[:5]
suffix = sha1[5:]
resp = requests.get(f'https://api.pwnedpasswords.com/range/{prefix}')
for line in resp.text.splitlines():
hash_suffix, count = line.split(':')
if hash_suffix == suffix:
return {'pwned': True, 'count': int(count)}
return {'pwned': False, 'count': 0}
# Check some passwords
for pwd in ['password123', 'correct-horse-battery-staple', 'j&82kL!mz9']:
result = check_password(pwd)
status = f'PWNED {result["count"]:,} times!' if result['pwned'] else 'Safe'
print(f' {pwd:<35} {status}')
Output:
password123 PWNED 247,516 times!
correct-horse-battery-staple PWNED 131 times!
j&82kL!mz9 Safe
2. Build a Password Strength Checker
def password_audit(password):
"""Comprehensive password check."""
issues = []
if len(password) < 12:
issues.append(f'Too short ({len(password)} chars, need 12+)')
if password.lower() == password:
issues.append('No uppercase letters')
if not any(c.isdigit() for c in password):
issues.append('No numbers')
if not any(c in '!@#$%^&*()_+-=[]{}|;:,.<>?' for c in password):
issues.append('No special characters')
# Check breach database
breach = check_password(password)
if breach['pwned']:
issues.append(f'Found in {breach["count"]:,} data breaches!')
score = max(0, 5 - len(issues))
return {
'score': f'{score}/5',
'issues': issues,
'verdict': 'Strong' if score >= 4 else 'Moderate' if score >= 2 else 'Weak'
}
result = password_audit('MyP@ssw0rd!')
print(f"Score: {result['score']} — {result['verdict']}")
for issue in result['issues']:
print(f" - {issue}")
3. Batch Check All Your Passwords
import time
def audit_password_file(filepath):
"""Check a list of passwords (one per line)."""
with open(filepath) as f:
passwords = [line.strip() for line in f if line.strip()]
pwned_count = 0
for pwd in passwords:
result = check_password(pwd)
if result['pwned']:
pwned_count += 1
print(f" !!! '{pwd[:3]}***' found in {result['count']:,} breaches")
time.sleep(0.1) # Be polite
print(f"\n{pwned_count}/{len(passwords)} passwords found in breaches")
4. Add to Registration Forms
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/check-password', methods=['POST'])
def check():
password = request.json.get('password', '')
result = check_password(password)
if result['pwned']:
return jsonify({
'safe': False,
'message': f'This password appeared in {result["count"]:,} data breaches. Choose a different one.'
}), 400
return jsonify({'safe': True, 'message': 'Password not found in known breaches'})
Frontend:
async function validatePassword(password) {
const resp = await fetch('/api/check-password', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({password})
});
const data = await resp.json();
if (!data.safe) {
document.getElementById('warning').textContent = data.message;
}
}
Rate Limits
- Password API: No authentication required
- Suggested: 1 request per 1500ms for bulk operations
- The API uses Cloudflare, so excessive requests may get rate-limited
Privacy
The k-anonymity model means:
- Your full password hash is never sent to the server
- Only the first 5 characters of the SHA-1 hash are transmitted
- The API returns ~500 matching suffixes — your password is hidden in the crowd
- Troy Hunt (the creator) has a great writeup on the security model
Combine With
- VirusTotal — check suspicious URLs
- WHOIS/RDAP — check domain age
- Full Security Toolkit — 5 free security APIs
Building free security tools. More: GitHub | Writing: Spinov001@gmail.com
Top comments (0)