├── Pipfile ├── README.md ├── check_for_password_leaks.py └── Pipfile.lock /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [dev-packages] 9 | 10 | 11 | 12 | [packages] 13 | 14 | requests-cache = "*" 15 | requests = "*" 16 | 17 | 18 | [requires] 19 | 20 | python_version = "3.5" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # check_for_password_leaks 2 | Securely check if passwords have been leaked online, You can manually enter passwords, or check a CSV file of passwords, which you could export from a password manager. 3 | 4 | 5 | 6 | Clone: 7 | 8 | git clone git@github.com:lukestanley/check_for_password_leaks.git 9 | 10 | cd check_for_password_leaks 11 | 12 | 13 | Install requests_cache with pip: 14 | 15 | 16 | pip install requests_cache 17 | 18 | python check_for_password_leaks.py 19 | 20 | 21 | Or pipenv, if that suits you sir! 22 | 23 | pipenv install 24 | 25 | pipenv shell 26 | 27 | 28 | To manually enter a password to your shell and securely check it for leaks online: 29 | 30 | python check_for_password_leaks.py type 31 | 32 | 33 | To check a passwords.csv file: 34 | 35 | python check_for_password_leaks.py type 36 | 37 | 38 | -------------------------------------------------------------------------------- /check_for_password_leaks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Securely check if passwords have been leaked online, 3 | You can manually enter passwords, or check a CSV file of passwords, 4 | which you could export from a password manager. 5 | """ 6 | 7 | import csv 8 | import requests 9 | from sys import argv 10 | import requests_cache 11 | from hashlib import sha1 12 | from os import name as os_name 13 | from os import system as run_command 14 | 15 | 16 | def main(): 17 | if 'type' in str(argv): 18 | check_manually_entered_passwords() 19 | else: 20 | print('Checking passwords.csv') 21 | tell_me_which_passwords_are_bad(get_passwords_from_csv()) 22 | 23 | 24 | def clear(): 25 | """Clears the console""" 26 | run_command('cls' if os_name == 'nt' else 'clear') 27 | 28 | 29 | def sha1_hash(string): 30 | """Gets a hex digest of a string""" 31 | return sha1(bytes(string, 'utf8')).hexdigest().lower() 32 | 33 | 34 | def unique_list(password_list): 35 | return list(set(password_list)) 36 | 37 | 38 | def get_hash_parts(password): 39 | """Hashes a password string, returning the first 5 characters of the hash, and the rest""" 40 | full_hash_string = sha1_hash(password) 41 | first_five_characters_of_hash = full_hash_string[0:5] 42 | rest_of_hash = full_hash_string[5:40] 43 | return first_five_characters_of_hash, rest_of_hash 44 | 45 | 46 | def password_is_found_in_leaks(password): 47 | """Searches an API of leaked passwords by sending the first 5 characters of a hash, 48 | then checks the rest of hash in results locally, so the full password hash is not exposed""" 49 | api = 'https://api.pwnedpasswords.com/range/' 50 | first_five_characters_of_hash, rest_of_hash = get_hash_parts(password) 51 | request = requests.get(api + first_five_characters_of_hash) 52 | results = request.text.lower() 53 | return rest_of_hash in results 54 | 55 | 56 | def get_passwords_from_csv(filename='passwords.csv', column_name='password'): 57 | """Get unique passwords from a column in a given CSV file""" 58 | passwords = [] 59 | with open(filename) as csv_file: 60 | reader = csv.DictReader(csv_file) 61 | for row in reader: 62 | passwords.append(str(row[column_name])) 63 | return passwords 64 | 65 | 66 | def tell_me_which_passwords_are_bad(passwords): 67 | for password in unique_list(passwords): 68 | if password_is_found_in_leaks(password): 69 | print('CHANGE THE PASSWORD:', password) 70 | 71 | 72 | def check_manually_entered_passwords(): 73 | password = input('Enter a password to check if it is leaked, or type exit: ') 74 | if password == 'exit': 75 | return 76 | clear() 77 | if password_is_found_in_leaks(password): 78 | print('CHANGE THE PASSWORD') 79 | else: 80 | print('Password not found in leaks') 81 | check_manually_entered_passwords() 82 | 83 | 84 | if __name__ == "__main__": 85 | requests_cache.install_cache() 86 | main() 87 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "c8c5a1da6dd95d25122e7efa3307e6c4b102c8fc2e639df74c271fa39a72c23e" 5 | }, 6 | "host-environment-markers": { 7 | "implementation_name": "cpython", 8 | "implementation_version": "3.5.2", 9 | "os_name": "posix", 10 | "platform_machine": "x86_64", 11 | "platform_python_implementation": "CPython", 12 | "platform_release": "4.13.0-32-generic", 13 | "platform_system": "Linux", 14 | "platform_version": "#35~16.04.1-Ubuntu SMP Thu Jan 25 10:13:43 UTC 2018", 15 | "python_full_version": "3.5.2", 16 | "python_version": "3.5", 17 | "sys_platform": "linux" 18 | }, 19 | "pipfile-spec": 6, 20 | "requires": { 21 | "python_version": "3.5" 22 | }, 23 | "sources": [ 24 | { 25 | "name": "pypi", 26 | "url": "https://pypi.python.org/simple", 27 | "verify_ssl": true 28 | } 29 | ] 30 | }, 31 | "default": { 32 | "certifi": { 33 | "hashes": [ 34 | "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296", 35 | "sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d" 36 | ], 37 | "version": "==2018.1.18" 38 | }, 39 | "chardet": { 40 | "hashes": [ 41 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", 42 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" 43 | ], 44 | "version": "==3.0.4" 45 | }, 46 | "idna": { 47 | "hashes": [ 48 | "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", 49 | "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" 50 | ], 51 | "version": "==2.6" 52 | }, 53 | "requests": { 54 | "hashes": [ 55 | "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", 56 | "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" 57 | ], 58 | "version": "==2.18.4" 59 | }, 60 | "requests-cache": { 61 | "hashes": [ 62 | "sha256:e9270030becc739b0a7f7f834234c73a878b2d794122bf76f40055a22419eb67", 63 | "sha256:fe561ca119879bbcfb51f03a35e35b425e18f338248e59fd5cf2166c77f457a2" 64 | ], 65 | "version": "==0.4.13" 66 | }, 67 | "urllib3": { 68 | "hashes": [ 69 | "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", 70 | "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" 71 | ], 72 | "version": "==1.22" 73 | } 74 | }, 75 | "develop": {} 76 | } 77 | --------------------------------------------------------------------------------