import argon2, os from .misc import time_function_pprint class PasswordHasher: ''' Argon2 password hasher and validator Attributes: config (dict): The settings used for the hasher Methods: get_config(key): Get the value of a config options set_config(key, value): Set a config option hash(password): hash a password and return the digest as a hex string verify(hash, password): verify a password and the password hash match iteration_test(string, passes, iterations): Time the hashing functionality ''' aliases = { 'iterations': 'time_cost', 'memory': 'memory_cost', 'threads': 'parallelism' } def __init__(self, iterations=16, memory=100, threads=os.cpu_count(), type=argon2.Type.ID): if not argon2: raise ValueError('password hashing disabled') self.config = { 'time_cost': iterations, 'memory_cost': memory * 1024, 'parallelism': threads, 'encoding': 'utf-8', 'type': type, } self.hasher = argon2.PasswordHasher(**self.config) def get_config(self, key): key = self.aliases.get(key, key) value = self.config[key] return value / 1024 if key == 'memory_cost' else value def set_config(self, key, value): key = self.aliases.get(key, key) self.config[key] = value * 1024 if key == 'memory_cost' else value self.hasher = argon2.PasswordHasher(**self.config) def hash(self, password: str): return self.hasher.hash(password) def verify(self, passhash: str, password: str): try: return self.hasher.verify(passhash, password) except argon2.exceptions.VerifyMismatchError: return False def iteration_test(self, string='hecking heck', passes=3, iterations=[8,16,24,32,40,48,56,64]): original_iter = self.get_config('iterations') for iteration in iterations: self.set_config('iterations', iteration) print('\nTesting hash iterations:', iteration) time_function_pprint(self.verify, self.hash(string), string, passes=passes) self.set_config('iterations', original_iter)