There has been a release of a great new feature recently when Troy Hunt launched V2 of his „Pwned Passwords“ service. The V2 is cool for many reasons like the unique and extended data. But in my opinion the most appreciated aspect is that it lowers the barrier to use it. Now there is a secure but still convenient way to check passwords against the database called range search. It ensures confidentiality because the password you like to check and even the SHA1 hash of it don’t leave your machine. And still there is no need to download the whole 500 million passwords. Simply said, you only need the first 5 chars of the hash of the password to check it against the database.

To ease this process and because I’d love to see more people care about their password quality, here is a guide to use the range search. To me it’s important that you don’t need to download any code that handles your valuable plain text passwords, that’s why I propose a really simple approch in some lines of python.

Let’s get going

Open a shell and start the python interpreter with python (or find another way to start python) and enter the following lines

import hashlib
from getpass import getpass

def create_hash():
  plain = getpass('your password: ')
  m = hashlib.sha1(plain.encode()).hexdigest().upper()
  return (m[0:5], m[5:])

The part starting with def defines a function that asks for your password, hashes it and splits the hash. You need to ensure that there are 2 spaces before 2nd to 4th line of this and that there is a blank line afterwards. In case you paste it into the interpreter, just hit enter to add a blank line more for safety. You need to see those >>> again. The python interpreter will show it somehow like this.

>>> import hashlib
>>> from getpass import getpass
>>>
>>> def create_hash():
...   plain = getpass('your password: ')
...   m = hashlib.sha1(plain.encode()).hexdigest().upper()
...   return (m[0:5], m[5:])
...
>>>
Interpreter output

Afterwards, without leaving the python interpreter you can just call the function which will then ask for the password to check (won’t be printed or recorded).

>>> create_hash()
your password:
('C5325', '5317BB11707D0F614696B3CE6F221D0E2F2')
>>>

This is the example for my personal password qwe123. Now, the first part C5325 is what you need for the range search, it makes up the URL to query.

https://api.pwnedpasswords.com/range/C5325

You can call that just with your browser, with wget or whatever tool you prefer for http. What you get back is on average 478 hashes that start with these 5 chars. This amount of hashes can be easily checked locally in your browser (Ctrl-F / Cmd-F) or with grep in your terminal.

Attention

Precisely, you don’t get complete hashes back, you only get the remaining 35 chars of the hash, the first 5 are missing. This is where the second part of the output of the python function comes into place, you have to use this for your local search. To mee, that was initially confusing because I missed this detail in Troy’s API description which lead me to the misbelief that ‘qwe123’ might be an unknown password.

5257AFC60DB63BF3584ED665B3AE64A761B:2
5317BB11707D0F614696B3CE6F221D0E2F2:595392
540DA8F934780A14EA80ADFF4E1A8F08050:1

Looks to me like my password is pretty famous, the hash is in the list. After the hash, there is a number that shows how often this password was found inside all the data that Troy collected. But honestly, this number does not matter, at least for security. If the hash is in the data you should consider your password publicly known which means it will be used in dictionary attacks. Go and change it!

My colleague Simon Kölsch dug a lot deeper into the topic of password security in context of the “have i been pwned?” database with his post “Enforce strong passwords!”. Go for it to get more into the details and see how all this can be fully integrated into your own services.

Disclaimer: The code above was tested on Mac OS with python 3.6 and 2.7, it is supposed to work on any python environment version 2.7 or newer. Use at own risk. Feedback welcome.