Skip to content

Instantly share code, notes, and snippets.

@Tenzer
Last active January 18, 2024 07:02
Show Gist options
  • Star 47 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Tenzer/b8aa3cfa09a7e1396a0661de6bf35633 to your computer and use it in GitHub Desktop.
Save Tenzer/b8aa3cfa09a7e1396a0661de6bf35633 to your computer and use it in GitHub Desktop.
LastPass Pwned Passwords checker

LastPass Pwned Passwords checker

This is a script for checking if any of the passwords you have stored in LastPass have been exposed through previous data breaches.

To use the script you need to have Python 3 installed and you need a CSV export of your LastPass vault. The export can be generated from the LastPass CLI with:

lpass export > lastpass.csv

or can be extracted with the browser plugin by going to the LastPass icon → More Options → Advanced → Export → LastPass CSV File (note that I did have problems getting this to work).

You can then run the above script on the file with:

python3 check-passwords.py lastpass.csv

or you can feed the passwords directly into the script from the LastPass CLI without writing them to disk by sending them to standard input:

lpass export | python3 check-passwords.py

Due to how the Pwned Passwords API works, the actual passwords will never leave your computer. A SHA1 checksum of the passwords will be generated and only the first 5 characters of that checksum will be sent to the API.

The script will print a line for each password found to be compromised along with the name of the site as saved in the vault along with the number of times the password has occurred in data breaches.

#!/usr/bin/env python3
import csv
import fileinput
from functools import lru_cache
from hashlib import sha1
from urllib.request import Request, urlopen
def get_credentials():
with fileinput.input() as csvfile:
for credential in csv.DictReader(csvfile, delimiter=',', quotechar='"'):
if credential.get('password'):
yield credential
def get_hash(password):
checksum = sha1(password.encode()).hexdigest().upper()
return checksum[:5], checksum[5:]
@lru_cache(maxsize=4096)
def get_range(prefix):
request = Request('https://api.pwnedpasswords.com/range/{}'.format(prefix))
request.add_header('User-Agent', 'tenzers-pwned-passwords-checker (+https://gist.github.com/Tenzer/b8aa3cfa09a7e1396a0661de6bf35633)')
response = urlopen(request, timeout=10).read()
data = dict()
for line in response.decode().splitlines():
split_line = line.split(':')
data[split_line[0]] = split_line[1]
return data
def main():
for credential in get_credentials():
prefix, suffix = get_hash(credential['password'])
range_data = get_range(prefix)
if range_data.get(suffix):
print(
'{} occurrences found of the password for "{}"'.format(
range_data.get(suffix),
credential.get('name'),
)
)
if __name__ == '__main__':
main()
@theDiverDK
Copy link

I had to change line 26 from:
response = urlopen(request, timeout=10).read()
to
response = urlopen(request, timeout=20).read()

In order not to get any timeout.

Thanks for the cool tool :)

@vertigo220
Copy link

vertigo220 commented Jul 12, 2018

When I run it, nothing seems to happen. Not sure if it's because it's not finding anything (seems like it should report that, though), or because I use KeePass and so had to do an export then remove all columns except the password column, and there are a bunch of blank rows, or if there's some other issue. Also, for some reason when I copy/pasted the code, the last line wasn't indented, so that gave me an error and I had to fix it. Just FYI for anyone having an issue to check, not the fault of the code itself obviously.

Edit: I removed the blank rows and am still having the same issue. Also, I have over 300 passwords in my main KeePass folder, but the exported csv file contains less than 200, so something is clearly not working right there. Might have to ask about this on their forums.

@enzyme01
Copy link

Hey - nice job - I was going to write something similar - had a quick google , came across this and does exactly what I need. Well done , thanks for sharing this.

@DesBlock
Copy link

When I run it, nothing seems to happen. Not sure if it's because it's not finding anything (seems like it should report that, though), or because I use KeePass and so had to do an export then remove all columns except the password column, and there are a bunch of blank rows, or if there's some other issue. Also, for some reason when I copy/pasted the code, the last line wasn't indented, so that gave me an error and I had to fix it. Just FYI for anyone having an issue to check, not the fault of the code itself obviously.

Edit: I removed the blank rows and am still having the same issue. Also, I have over 300 passwords in my main KeePass folder, but the exported csv file contains less than 200, so something is clearly not working right there. Might have to ask about this on their forums.

This is for LastPass, unless the KeyPass XML uses the same tags as LastPass, this wont work for you.

@clarkd
Copy link

clarkd commented Jan 17, 2019

Perfect, thanks - still works as of today.

@Anguel
Copy link

Anguel commented Jan 18, 2019

Thank you very much! Export from LastPass via browser plugin worked for me, however LastPass gives you a .html file. You have to copy and paste it from browser to e.g. Notepad++ and save it as .csv. See also here:
https://lastpass.com/support.php?cmd=showfaq&id=1206

Another problem is that the file is in UTF-8 and your script does not use UTF-8. I worked around this by extending line 11 like this:

with fileinput.input(openhook=fileinput.hook_encoded("utf-8")) as csvfile:

I hope this helps :-)

@Fethbita
Copy link

One thing to consider is if you want to do this offline, it should still be possible. You could download the Pwned Passwords list and manually compare the hashes while you have your computer disconnected from the internet, which I think would be a safer option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment