Package: pwnedpasswords
pwnedpasswordsis a small Python wrapper and command line utility that lets you check if a passphrase has been pwned using thePwned Passwords v2 API. All provided password data isk-anonymizedbefore sending to the API, so plaintext passwords never leave your computer.Fromhttps://haveibeenpwned.com/API/v2#PwnedPasswords:Pwned Passwords are more than half a billion passwords which have previously been exposed in data breaches. The service is detailed in thelaunch blog postthenfurther expanded on with the release of version 2. The entire data set isboth downloadable and searchable online via the Pwned Passwords page.Installationpwnedpasswords is available for download throughPyPi. You can install it right away using pip.pipinstallpwnedpasswordsUsageimportpwnedpasswordspwnedpasswords.check("testing 123")# Returns 1Security NoteNo plaintext passwords ever leave your machine using pwnedpasswords.How does that work? Well, the Pwned Passwords v2 API has a pretty coolk-anonymityimplementation.Fromhttps://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/:Formally, a data set can be said to hold the property of k-anonymity, if for every record in a released table, there are k − 1 other records identical to it.This allows us to only provide the first 5 characters of the SHA-1 hash of the password in question. The API then responds with a list of SHA-1 hash suffixes with that prefix. On average, that list contains 478 results.People smarter than I am have usedmathto prove that 5-character prefixes are sufficient to maintain k-anonymity for this database.In short: your plaintext passwords are protected if you use this library. You won't leak enough data to identity which passwords you're searching for.Notespwnedpasswords automatically checks if your provided input looks like a SHA-1 hash. If it does, it won't do any further processing. If it looks like plain text, it'll automatically hash it before sending it to the Pwned Passwords API.If you'd like to provide an already-hashed password as input to pwnedpasswords, you don't need to do anything--pwnedpasswords will detect that it looks like a SHA-1 hash and won't hash it again before sending it to therangeendpoint.pwnedpasswords.check("b8dfb080bc33fb564249e34252bf143d88fc018f")Likewise, if a password looks like a SHA-1 hash (i.e., matches the regex[0-9a-fA-F]{40}) but is actually a user-provided password, setplain_texttoTrue, so that the library knows to hash it before sending it to the API.pwnedpasswords.check("1231231231231231231231231231231231231231",plain_text=True)DetailscheckThis is the preferred method. By default, thecheckmethod uses thehttps://api.pwnedpasswords.com/range/endpoint, which isk-anonymous.pwnedpasswords.check("mypassword")# 34729If you'd like to force pwnedpasswords to use the search endpoint instead (https://api.pwnedpasswords.com/pwnedpassword/), set theanonymousparameter toFalse.pwnedpasswords.check("password",anonymous=False)# 3303003You might want to do this if you'd prefer faster response times, and aren't that worried about leaking passwords you're searching for over the network.If you'd like direct access to the search and range endpoints, you can also call them directly.rangepwnedpasswords.range("098765")# outputs a dictionary mapping SHA-1 hash suffixes to frequency countsCommand Line Utilitypwnedpasswords comes bundled with a handy command line utility.$pwnedpasswords123456password240Output is simply the number of entries found in the Pwned Passwords database.If you'd like to prevent input from appearing in your history, specify the--stdinargument to provide input via stdin (h/t to@tveastmanfor requesting this).$pwnedpasswords--stdin
mypassword34729For help, just provide-has a command-line argument.$pwnedpasswords-h
usage:pwnedpasswords[-h][--verbose][--plain-text](--stdin|password)ChecksPwnedPasswordsAPItoseeifprovidedplaintextdatawasfoundina
databreach.

positionalarguments:passwordThepasswordorhashedpasswordtosearchfor.

optionalarguments:-h,--helpshowthishelpmessageandexit--verboseDisplayverboseoutput.--plain-textSpecifythattheprovidedinputisplaintext,evenifitlookslikeaSHA-1hash.--stdinReadprovidedinputfromstdin.NoteThe CLI returns an exit code equal to the base-10 log of the result count, plus 1. If there are no matches in the API, the exit status will be0. While returning the base-10 log might seem odd, note that most systems require exit status codes to be in the range 0-127, and I wanted the status code to providesomeindication for severity. log(N) seemed to be a good tradeoff. The exit status is log(N)+1 since there are plenty of matches in the database with 1 match.If you'd like to take a look under the hood to make sure things are working as they should, set the--verboseflag.$pwnedpasswords123456password--verbose
INFO:pwnedpasswords.pwnedpasswords:https://api.pwnedpasswords.com/range/5052C
INFO:pwnedpasswords.pwnedpasswords:Entryfound240ThanksSpecial thanks toTroy Huntfor collecting this data and providing this service.AuthorsDan Loewenherz / (@dlo)See alsodjango-pwnedpasswords-validator, a validator that checks user passwords against the Pwned Passwords API using this library.LicenseApache License, Version 2.0. SeeLICENSEfor details.