python: Add initial prototype
This commit is contained in:
0
python/passgeny/__init__.py
Normal file
0
python/passgeny/__init__.py
Normal file
40
python/passgeny/bhash.py
Normal file
40
python/passgeny/bhash.py
Normal file
@ -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
|
||||||
47
python/passgeny/passgeny.py
Normal file
47
python/passgeny/passgeny.py
Normal file
@ -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")))
|
||||||
0
python/passgeny/phogen.py
Normal file
0
python/passgeny/phogen.py
Normal file
Reference in New Issue
Block a user