Skip to content

Commit c916ec6

Browse files
authored
Create password_checker.py
1 parent 45164f0 commit c916ec6

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

password_checker.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python3
2+
3+
'''
4+
Check passwords against the popular "Have I been Pwned?" website to find out if
5+
it has been seen in any of the recorded data breaches. It does so by leveraging
6+
the website's API to send a only fragment of a hash of the password (not the
7+
password itself) and compare the results for that particular signature.
8+
9+
Provide the passwords you'd like to check as arguments to the script. You may
10+
write as many or as little as you like, however note that commands entered
11+
at the command line are registered in your computer. There are ways of deleting
12+
the history of entered commands in your machine but for better privacy you can
13+
provide a CSV file instead (which you should keep safe or delete afterwards).
14+
15+
This script is intended to work with CSV files exported by the KeePassXC
16+
password manager. This means that the file should contain at least 2 columns
17+
with the headers "Title" and "Password", title being the website or service.
18+
19+
usage: password_checker.py [-h] [-f FILE] [-v] [passwords [passwords ...]]
20+
21+
positional arguments:
22+
passwords A list of passwords to check
23+
24+
optional arguments:
25+
-h, --help show this help message and exit
26+
-f FILE, --file FILE Path to an existing CSV file containing the passwords
27+
to check
28+
-v, --verbose prints additional information to terminal output
29+
'''
30+
31+
import requests
32+
import argparse
33+
import logging
34+
import hashlib
35+
import pathlib
36+
import csv
37+
import sys
38+
39+
def main():
40+
args = parse_arguments()
41+
42+
if args.verbose:
43+
logging.basicConfig(level=logging.DEBUG)
44+
logging.debug(args)
45+
46+
if args.file:
47+
file = args.file[0]
48+
49+
exists = pathlib.Path(file).is_file()
50+
if (not exists or not file.endswith('csv')):
51+
print('File not found, please check the filename and path')
52+
return False
53+
54+
with open(file) as f:
55+
dict_file = csv.DictReader(f)
56+
for row in dict_file:
57+
title = row['Title']
58+
password = row['Password']
59+
logging.debug(f"Checking password for {title}")
60+
have_i_been_pwned(password, service=title)
61+
62+
if args.passwords:
63+
for password in args.passwords:
64+
have_i_been_pwned(password)
65+
66+
67+
def have_i_been_pwned(password, service=None):
68+
'Checks and prints out to console any matches found for a password'
69+
70+
head, tail = hash_password(password)
71+
response = send_request(head)
72+
match = check_hash(response, tail)
73+
74+
if not service:
75+
service = f'{password[:3]}...'
76+
77+
if (match):
78+
print(f'Found match for "{service}" {match} times!')
79+
80+
81+
def send_request(head):
82+
'''Sents out a request containing the first 5 characters of the hash from
83+
a password to "Have I Been Pwned?". The results are compared against the
84+
rest of the hash for that passwords for any matches found.'''
85+
86+
url = f'https://api.pwnedpasswords.com/range/{head}'
87+
response = requests.get(url)
88+
if response.status_code != 200:
89+
raise RuntimeError(f'Error in request: {str(res.status_code)}')
90+
91+
return response.text
92+
93+
94+
def check_hash(hash_list, tail):
95+
'''Receives a list of potential hashes found on online data breaches, and
96+
compares them with the tail of a hash for a particular password provided.
97+
If a match is found here, your password has been compromised! Otherwise it
98+
returns False.'''
99+
100+
hashes = (line.split(':') for line in hash_list.splitlines())
101+
for hash, count in hashes:
102+
if hash == tail:
103+
return count
104+
105+
return False
106+
107+
108+
def hash_password(password):
109+
'''Creates a hash of the provided password and splits the resulting string
110+
at the first 5 characters, and returning both parts of the string'''
111+
112+
password_hash = hashlib.sha1(password.encode('utf-8')).hexdigest().upper()
113+
head, tail = password_hash[:5], password_hash[5:]
114+
return head, tail
115+
116+
117+
def parse_arguments():
118+
parser = argparse.ArgumentParser()
119+
120+
parser.add_argument('passwords',
121+
help='A list of passwords to check',
122+
nargs='*',
123+
)
124+
parser.add_argument('-f', '--file',
125+
help='Path to an existing CSV file containing the passwords to check',
126+
nargs=1
127+
)
128+
parser.add_argument('-v', '--verbose',
129+
help='prints additional information to terminal output',
130+
action='store_true'
131+
)
132+
return parser.parse_args()
133+
134+
135+
if __name__ == '__main__':
136+
main()

0 commit comments

Comments
 (0)