diff --git a/python/passgeny/__init__.py b/python/passgeny/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/passgeny/bhash.py b/python/passgeny/bhash.py new file mode 100644 index 0000000..327d9b1 --- /dev/null +++ b/python/passgeny/bhash.py @@ -0,0 +1,40 @@ +import math + +class BhashException(Exception): pass + +class Bhash: + """ + Hash manipulation class. + + The hash is internally treated as a big-endinan integer. This allows for + easy implementation of operations such as modulo. + + Note: The consumed bits are calculated using math.log() so the exact number + may be inprecise. + """ + def __init__(self): + self.bhash = None + self.bits_avail = 0 + self.bits_used = 0.0 + + def from_bytes(self, buf: bytes): + """ + Initialize a Bhash from a bytes object + """ + self.bhash = int.from_bytes(buf, byteorder='big') + self.bits_avail = len(buf) * 8 + self.bits_used = 0.0 + + def modulo(self, mod: int) -> int: + """ + Treat the hash as a bigint and divide it by mod. The hash is updated + with the division result while the module value is returned. + """ + self.bits_used += math.log(mod) / math.log(2) + if self.bits_used > self.bits_avail: + raise BhashException("Consumed all bits in hash") + + r = self.bhash % mod + self.bhash //= mod + + return r diff --git a/python/passgeny/passgeny.py b/python/passgeny/passgeny.py new file mode 100644 index 0000000..5eb7327 --- /dev/null +++ b/python/passgeny/passgeny.py @@ -0,0 +1,47 @@ +# +# Requirements: argon2-cffi +# + +import argon2 +import argparse +import getpass +import sys + +# +# WARNING: Changing any of the parameters below will affect password generation +# + +# 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 = 64 + +def argon2_hash(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 passgeny_hash(master_password, token_list): + message = b'' + + # Construct the message to be hashed + for x in token_list: + message += x.encode() + b'\0' + + message += master_password.encode() + b'\0' + return argon2_hash(message) + +mpw = getpass.getpass("Master password: ") +print(passgeny_hash(mpw, ("1", "2", "3"))) diff --git a/python/passgeny/phogen.py b/python/passgeny/phogen.py new file mode 100644 index 0000000..e69de29