# # Requirements: argon2-cffi # import argon2 import argparse import re from . import bhash, phogen # # WARNING: Changing any of the parameters below will affect password generation # PASSGENY_DEFAULT_PATTERN = "^6p^6p^6ps2p100d" # List of special characters PASSGENY_SPECIAL = " +-./=_" # Default SALT to use; this cannot be random since passgeny must # generate predictable passwords PASSGENY_SALT_DEFAULT = b"Passgeny v1" # Memory cost expresssed in kb PASSGENY_ARGON2_MEMORY_COST = 8192 # Number of iterations PASSGENY_ARGON2_TIME_COST = 6 # Number of parallel threads PASSGENY_ARGON2_PARALLEL = 4 # Hash length - 512 bits by default PASSGENY_ARGON2_HASH_LEN = 128 class PassgenyInvalidPattern(Exception): pass class Passgeny: def __init__(self, master_password): self.master_password = self.__argon2_hash(master_password.encode()) self.pattern = PASSGENY_DEFAULT_PATTERN def generate(self, domain, user, *tokens): bh = bhash.Bhash() bh.from_bytes(self.__hash([domain, user, *tokens])) return self.__generate_from_pattern(bh) def set_pattern(self, pattern): self.pattern = pattern def __argon2_hash(self, message): return argon2.low_level.hash_secret_raw( message, PASSGENY_SALT_DEFAULT, type = argon2.Type.ID, memory_cost = PASSGENY_ARGON2_MEMORY_COST, time_cost = PASSGENY_ARGON2_TIME_COST, parallelism = PASSGENY_ARGON2_PARALLEL, hash_len = PASSGENY_ARGON2_HASH_LEN) def __hash(self, token_list): message = b'' # Construct the message to be hashed for x in token_list: message += x.encode() + b'\0' # Append the SHA256 of the master password message += self.master_password # Compute the hash using argon2 return self.__argon2_hash(message) def __generate_from_pattern(self, bh): pattern = self.pattern flags = "" password = "" def gen_phogen(match): plen = 1 if match[1] == "" else int(match[1]) return phogen.encode(bh, plen) def gen_special(match): slen = 1 if match[1] == "" else int(match[1]) return PASSGENY_SPECIAL[bh.modulo(len(PASSGENY_SPECIAL))] def gen_decimal(match): dlen = 1 if match[1] == "" else int(match[1]) return "{}".format(bh.modulo(dlen)) def gen_hexstr(match): x = "" for l in range(int(match[1])): x += "{:x}".format(bh.modulo(16)) return x def gen_capitalize(match): nonlocal flags flags += '^' return None pattern_list = [ [ '([0-9]*)p', gen_phogen ], [ '([0-9]*)s', gen_special ], [ '([0-9]+)d', gen_decimal ], [ '([0-9]+)x', gen_hexstr ], [ '\^', gen_capitalize ], ] while pattern: pw = None for pm in pattern_list: if match := re.match(pm[0], pattern): break if not match: raise PassgenyInvalidPattern("Invalid pattern: {}".format(self.pattern)) pw = pm[1](match) pattern = pattern[len(match[0]):] if not pw: continue if "^" in flags: pw = pw.capitalize() password += pw flags = "" return password