If you collect passwords in a web application, it's worth checking them against Troy Hunt's [Pwned Passwords](https://haveibeenpwned.com/Passwords) database — a list of hundreds of millions of passwords exposed in real-world data breaches. If a user's chosen password shows up there, it's a bad password no matter how "strong" it looks, because it's already in every credential-stuffing wordlist attackers use.
The good news: you never send the actual password (or even its full hash) to the API. Here's how the check works, and how to implement it in both **Xbasic (Alpha Anywhere)** and **Python**.
Because you only ever transmit a 5-character prefix, the API never sees enough of the hash to reconstruct the original password, and Have I Been Pwned has no way of knowing which specific password you checked.
Compute the SHA-1 hash client-side (via JavaScript), send only the first 5 characters to the range endpoint, and search for the remaining 35 characters in the response body:
xbasic
if AT(hash_newPassword35,hashlist) > 0 ' found
err_msg = "This password is not allowed because it was found in a list of known compromised passwords."
goto endofscript
end if
A couple of implementation notes worth calling out for anyone adapting this:
Suggested Alpha Anywhere panel
```python
import hashlib
import requests
def is_pwned(password: str) -> bool:
sha1 = hashlib.sha1(password.encode("utf-8")).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
response = requests.get(
f"https://api.pwnedpasswords.com/range/{prefix}",
headers={"Add-Padding": "true"}, # optional: mitigates response-size timing attacks
timeout=5,
)
response.raise_for_status()
for line in response.text.splitlines():
candidate_suffix, count = line.split(":")
if candidate_suffix == suffix:
return True # found in breach corpus
return False
if __name__ == "__main__":
pwd = "password123"
if is_pwned(pwd):
print("This password has appeared in a data breach — choose another.")
else:
print("Password not found in known breaches.")
```
Both implementations follow the same three steps — hash, truncate, compare — and neither ever exposes the plaintext password or a reversible hash over the wire. It's a small addition to a registration or password-change flow, and it closes off one of the more common ways accounts get compromised: reusing a password that's already sitting in an attacker's wordlist.