How to securely check your LastPass vault against the Pwned Passwords database

A one-liner shell script that reports compromised passwords

Background

Ever since I saw that there is a database called Pwned Passwords in which everyone can check if a particular password is leaked, I wanted to know if any of mine is compromised. Unfortunately, while LastPass reports if I have accounts that are affected in a leak, it does not check the passwords themselves.

Since I can export my LastPass vault as a csv file, it was only a step forward to write a one-liner that runs the check for every password contained.

It’s Linux only, and while I’m not a bash expert, I believe it does its job well and is built on top of simple programs.

It might not handle all edge cases perfectly, especially when the password contains a comma, but that should be rare.

Pwned passwords database

The database itself is run by Troy Hunt. He uploads leaked passwords from known data breaches and maintains an API where you can securely check if a given password is in any of the data sets.

If it is, then do not use it, and if you are providing a service, you should prevent your users from using it.

How can such a service work without sending the password itself? When you check a candidate, instead of sending it directly, you send the hash of it. This way the sensitive information never leaves your computer.

How to check your passwords

First, you need to export the LastPass vault using More options -> Advanced -> Export -> LastPass CSV File, then copy-paste the contents to a text editor and save it. As for the location, it’s best to choose an ephemeral place, like /tmp, as it will get wiped automatically. For the name, I’ll use fp.csv in the examples.

You’ll need some command-line tools that might not be available by default. Install them if you don’t have them already:

sudo apt install -y csvtool curl

Then open a terminal, navigate to the location of the file and run this script:

csvtool namedcol url,password fp.csv | tail -n +2 | grep .*,. | while read line; do hash=$(printf "%s" "$(echo $line | rev | cut -d, -f1 | rev)" | sha1sum | awk '{print $1}' | tr a-z A-Z); if [[ -n $((curl -s https://api.pwnedpasswords.com/range/$(echo $hash | cut -c1-5) || echo "error" 1>&2) | grep $(echo $hash | cut -c6-)) ]]; then echo "${line}"; fi ; done

It takes a minute or two, but it will output every compromised url-password pairs.

Finally, don’t forget to delete the LastPass CSV:

rm fp.csv

Change the compromised passwords.

Alternatives

I’ve found this gist that provides a similar functionality, but using Python. For one-time tasks like this I prefer a one-liner bash script rather than something I need to download and run, but it should be functionally equivalent (and might handle some edge-cases better).

Technical details

Pwned passwords API

To check a password, you actually check the SHA-1 of it, so no secret is transferred plain-text. Also, the API uses a so-called k-anonimity model, which in a nutshell works like this: when querying a hash, you supply only the first 5 characters of it, and get back a list of all known hashes with that prefix. This makes it unlikely for the service provider to know if a compromised password is found in the database. Personally, I find it fascinating, read more about it here if interested.

Breaking down the script

As you should not run anything on your passwords you don’t understand, let’s break down the script, step by step.

Read the csv, get the url and the password fields, skip the first line (which is the header), then filter all the rows without a password (for example, secure notes):

csvtool namedcol url,password fp.csv | tail -n +2 | grep .*,.

Iterate over all rows:

while read line; do

Calculate the hash:

hash=$(printf "%s" "$(echo $line | rev | cut -d, -f1 | rev)" | sha1sum | awk '{print $1}' | tr a-z A-Z);

This might need some more explanation. The echo $line | rev | cut -d, -f1 | rev outputs only the password (remember, the url and the password is separated by a comma). Then the sha1sum produces the hash itself, but with some extra data. The awk part cuts that off, and the tr converts to all uppercase, as the API returns everything that way.

Make the call to the API:

curl -s https://api.pwnedpasswords.com/range/$(echo $hash | cut -c1-5) || echo "error" 1>&2

The echo $hash | cut -c1-5 returns the first 5 characters of the hash, then the curl fetches the result. The last part, || echo makes sure that an error is printed if there is a problem with the call.

Find the hash without the prefix in the result:

| grep $(echo $hash | cut -c6-)

And finally, check if the grep returned any matches, in which case the hash is present in the database. The echo "${line}" prints the url-password pair in that case:

if [[ -n $(...) ]]; then
02 October 2018

Interesting article?

Get hand-crafted emails on new content!