Skip to content

Instantly share code, notes, and snippets.

@nfitzen
Last active June 19, 2025 21:44
Show Gist options
  • Select an option

  • Save nfitzen/f06b41108e72f9c346bca23c13a97a4c to your computer and use it in GitHub Desktop.

Select an option

Save nfitzen/f06b41108e72f9c346bca23c13a97a4c to your computer and use it in GitHub Desktop.
Looks up passwords on Have I Been Pwned.
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# MIT License
#
# Copyright (C) 2021-2022, 2025 Nathaniel Fitzenrider <https://github.com/nfitzen>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice (including the
# next paragraph) shall be included in all copies or substantial
# portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Looks up passwords on Have I Been Pwned's passwords service.
# This way, I have a somewhat auditable program for checking whether a
# password was breached. It's certainly easier to inspect than a webpage's JS.
# See <https://haveibeenpwned.com/Passwords>.
# Note: might not be safe, since I didn't check the security
# with respect to, e.g., memory or terminal usage.
import httpx
from hashlib import sha1
from getpass import getpass
from typing import NamedTuple, Optional
HIBP_API_URL = "https://api.pwnedpasswords.com/range/"
class PasswordData(NamedTuple):
suffix: str
hit_rate: int
def find_password(data: str, suffix: str) -> Optional[PasswordData]:
"""Finds the password suffix in the response returned by the HIBP API.
This function handles parsing of the response."""
if not data:
return
suffix = suffix.upper()
for line in data.split("\n"):
line_data = line.split(":")
current_suffix = line_data[0]
hit_rate = int(line_data[1])
if current_suffix == suffix and hit_rate > 0:
return PasswordData(current_suffix, hit_rate)
def hibp_lookup(pwd: str) -> Optional[PasswordData]:
"""Looks up a password with the HIBP pwned passwords API.
Returns None if the password wasn't found."""
pwd_hash = sha1(bytes(pwd, "utf-8")).hexdigest().upper()
prefix = pwd_hash[:5]
suffix = pwd_hash[5:]
headers = {"Add-Padding": "true"}
request = httpx.get(HIBP_API_URL + prefix, headers=headers)
request.raise_for_status()
data = request.text
return find_password(data, suffix)
def main():
while True:
try:
pwd = getpass()
except (KeyboardInterrupt, EOFError):
print("\nExiting...")
break
if pwd == "":
print("Exiting...")
break
lookup = hibp_lookup(pwd)
if lookup is None:
print(
"Password wasn't found. Doesn't necessarily mean your password is secure."
)
continue
number = lookup.hit_rate
print("PASSWORD FOUND. Make sure to take the proper precautions!")
if number > 1:
print(f"It was found {number} times.")
else:
print("It was found 1 time.")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment