import getpass import json import pathlib import random import string import sys # name of the file where we store the pw database PWDB_FLNAME = pathlib.Path('pwdb.json') # list of valid characters for salt (only ASCII letters + digits + punctuation) CHARS = string.ascii_letters + string.digits + string.punctuation # length of salt SALT_LENGTH = 5 def get_credentials(): # get input from terminal username = input('Enter your username: ') # get password using the appropriate module, so that typed characters are not # echoed to the terminal password = getpass.getpass('Enter your password: ') return (username, password) def authenticate(username, pass_text, pwdb): # get the salt from the database salt = pwdb[username][1] # calculate hash and compare with stored hash return pwhash(pass_text, salt) == pwdb[username][0] def add_user(username, pwdb): # do not try to add a username twice if username in pwdb: raise Exception(f'Username already exists [{username}]!') else: password = getpass.getpass(f'Enter password for {username}: ') salt = get_salt() pwdb[username] = (pwhash(password,salt), salt) return pwdb def read_pwdb(pwdb_path): # try to read from the database # if anything happens, report the error! if not pwdb_path.exists(): initialize_pwdb(pwdb_path) try: with open(pwdb_path, 'rt') as pwdb_file: pwdb = json.load(pwdb_file) except json.decoder.JSONDecodeError as exc: # this happens when the json data is invalid raise Exception(f'Invalid database {pwdb_path}: {exc}') except Exception as exc: # this is a catch-all condition raise Exception(f'Unkown error reading {pwdb_path}: {exc}') return pwdb def write_pwdb(pwdb, pwdb_path): with open(pwdb_path, 'wt') as pwdb_file: json.dump(pwdb, pwdb_file) def pwhash(pass_text, salt): # simple additive hash -> very insecure! hash_ = 0 full_pass_text = pass_text + salt for idx, char in enumerate(full_pass_text): # use idx as a multiplier, so that shuffling the characters returns a # different hash hash_ += (idx+1)*ord(char) return hash_ def get_salt(): salt_chars = random.choices(CHARS, k=SALT_LENGTH) return ''.join(salt_chars) def initialize_pwdb(pwdb_path): write_pwdb({}, pwdb_path) def main(pwdb_path): # load the password database from file pwdb = read_pwdb(pwdb_path) # if we are passed an argument, we want to add a new user if len(sys.argv) > 1: pwdb = add_user(sys.argv[1], pwdb) write_pwdb(pwdb, pwdb_path) return # ask for credentials username, password = get_credentials() if username not in pwdb: print('Wrong username!') return # try to authenticate if authenticate(username, password, pwdb): print('Successfully authenticated!') else: # report wrong password print('Wrong password!') return if __name__ == '__main__': main(PWDB_FLNAME)