diff --git a/auth.py b/auth.py index 7029239..5408393 100644 --- a/auth.py +++ b/auth.py @@ -1,57 +1,88 @@ +import getpass import json +import pathlib +import random +import string import sys -from getpass import getpass -from hashlib import sha256 -PWDB_PATH = 'pwdb.json' +# 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: ') - hashed_password = pwhash(getpass('Enter your password: ')) - return (username, hashed_password) + # 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, hashed_password, pwdb): - return hashed_password == pwdb[username] +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): - pwdb[username] = pwhash(getpass(f'Enter password for {username}: ')) + # 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: - pwdb_file = open(PWDB_PATH, 'rt') - pwdb = json.load(pwdb_file) - except Exception: +def read_pwdb(pwdb_path): + if not pwdb_path.exists(): pwdb = {} + else: + with open(pwdb_path, 'rt') as pwdb_file: + pwdb = json.load(pwdb_file) return pwdb -def write_pwdb(pwdb, PWDB_PATH): - pwdb_file = open(PWDB_PATH, 'wt') - json.dump(pwdb, pwdb_file) +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 pwhash(pwd): - encoded_pwd = pwd.encode("utf-8") - m = sha256() - m.update(encoded_pwd) - return m.hexdigest() +def get_salt(): + salt_chars = random.choices(CHARS, k=SALT_LENGTH) + return ''.join(salt_chars) - - -if __name__ == "__main__": - PWDB_PATH = 'pwdb.json' - pwdb = read_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) - else: - username, password = get_credentials() - if username not in pwdb: - print('Wrong username!') - else: - if authenticate(username, password, pwdb): - print('Successfully authenticated!') - else: - print('Wrong password!') + write_pwdb(pwdb, pwdb_path) + return + + # ask for credentials + username, password = get_credentials() + if username not in pwdb or not authenticate(username, password, pwdb): + print('Wrong username or password!') + else: + print('Successfully authenticated!') + + return + +if __name__ == '__main__': + main(PWDB_FLNAME)