From 19f07309132de54ab135666e6ffe404021ef2498 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Mon, 27 Mar 2023 10:50:38 +0000 Subject: [PATCH] crypto.py: reimplement as class --- mmgen/addrfile.py | 7 +- mmgen/addrlist.py | 4 +- mmgen/crypto.py | 716 +++++++++++++++++---------------- mmgen/data/version | 2 +- mmgen/fileutil.py | 7 +- mmgen/opts.py | 14 +- mmgen/passwdlist.py | 4 +- mmgen/seed.py | 4 +- mmgen/seedsplit.py | 7 +- mmgen/subseed.py | 4 +- mmgen/tool/coin.py | 8 +- mmgen/tool/filecrypt.py | 10 +- mmgen/tool/fileutil.py | 8 +- mmgen/tool/mnemonic.py | 4 +- mmgen/tool/util.py | 8 +- mmgen/wallet/brain.py | 3 +- mmgen/wallet/dieroll.py | 4 +- mmgen/wallet/enc.py | 20 +- mmgen/wallet/incog_base.py | 15 +- mmgen/wallet/mmgen.py | 10 +- test/misc/get_passphrase.py | 12 +- test/misc/oneshot_warning.py | 4 +- test/misc/term.py | 4 +- test/unit_tests_d/ut_scrypt.py | 10 +- 24 files changed, 450 insertions(+), 439 deletions(-) diff --git a/mmgen/addrfile.py b/mmgen/addrfile.py index 89a16953..7ad0173f 100755 --- a/mmgen/addrfile.py +++ b/mmgen/addrfile.py @@ -49,12 +49,11 @@ class AddrFile(MMGenObject): self.infile = None def encrypt(self): - from .crypto import mmgen_encrypt,mmenc_ext - from .globalvars import g - self.fmt_data = mmgen_encrypt( + from .crypto import Crypto + self.fmt_data = Crypto().mmgen_encrypt( data = self.fmt_data.encode(), desc = f'new {self.parent.desc} list' ) - self.ext += f'.{mmenc_ext}' + self.ext += f'.{Crypto.mmenc_ext}' @property def filename(self): diff --git a/mmgen/addrlist.py b/mmgen/addrlist.py index ac033adc..b09eab3b 100755 --- a/mmgen/addrlist.py +++ b/mmgen/addrlist.py @@ -312,11 +312,11 @@ class AddrList(MMGenObject): # Address info for a single seed ID scramble_key = self.proto.coin.lower() else: scramble_key = (self.proto.coin.lower()+':','')[is_btcfork] + self.al_id.mmtype.name - from .crypto import scramble_seed + from .crypto import Crypto if self.proto.testnet: scramble_key += ':' + self.proto.network self.dmsg_sc('str',scramble_key) - return scramble_seed(seed,scramble_key.encode()) + return Crypto().scramble_seed(seed,scramble_key.encode()) def idxs(self): return [e.idx for e in self.data] diff --git a/mmgen/crypto.py b/mmgen/crypto.py index dfd4fbda..8087dd98 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -39,368 +39,376 @@ from .util import ( oneshot_warning, ) -mmenc_ext = 'mmenc' -scramble_hash_rounds = 10 -salt_len = 16 -aesctr_iv_len = 16 -aesctr_dfl_iv = int.to_bytes(1,aesctr_iv_len,'big') -hincog_chk_len = 8 +class Crypto: -# Scrypt params: 'id_num': [N, r, p] (N is an exponent of two) -# NB: hashlib.scrypt in Python (>=v3.6) supports max N value of 14. This means that -# for hash presets > 3 the standalone scrypt library must be used! -_hp = namedtuple('scrypt_preset',['N','r','p']) -hash_presets = { - '1': _hp(12, 8, 1), - '2': _hp(13, 8, 4), - '3': _hp(14, 8, 8), - '4': _hp(15, 8, 12), - '5': _hp(16, 8, 16), - '6': _hp(17, 8, 20), - '7': _hp(18, 8, 24), -} + mmenc_ext = 'mmenc' + scramble_hash_rounds = 10 -class pwfile_reuse_warning(oneshot_warning): - message = 'Reusing passphrase from file {!r} at user request' - def __init__(self,fn): - oneshot_warning.__init__(self,div=fn,fmt_args=[fn],reverse=True) + salt_len = 16 + aesctr_iv_len = 16 + aesctr_dfl_iv = int.to_bytes(1,aesctr_iv_len,'big') + hincog_chk_len = 8 -def get_hash_params(hash_preset): - if hash_preset in hash_presets: - return hash_presets[hash_preset] # N,r,p - else: # Shouldn't be here - die(3,f"{hash_preset}: invalid 'hash_preset' value") + mmenc_salt_len = 32 + mmenc_nonce_len = 32 -def sha256_rounds(s,n): - from hashlib import sha256 - for i in range(n): - s = sha256(s).digest() - return s + # Scrypt params: 'id_num': [N, r, p] (N is an exponent of two) + # NB: hashlib.scrypt in Python (>=v3.6) supports max N value of 14. This means that + # for hash presets > 3 the standalone scrypt library must be used! + _hp = namedtuple('scrypt_preset',['N','r','p']) + hash_presets = { + '1': _hp(12, 8, 1), + '2': _hp(13, 8, 4), + '3': _hp(14, 8, 8), + '4': _hp(15, 8, 12), + '5': _hp(16, 8, 16), + '6': _hp(17, 8, 20), + '7': _hp(18, 8, 24), + } -def scramble_seed(seed,scramble_key): - import hmac - step1 = hmac.digest(seed,scramble_key,'sha256') - if g.debug: - msg(f'Seed: {seed.hex()!r}\nScramble key: {scramble_key}\nScrambled seed: {step1.hex()}\n') - return sha256_rounds( step1, scramble_hash_rounds ) + class pwfile_reuse_warning(oneshot_warning): + message = 'Reusing passphrase from file {!r} at user request' + def __init__(self,fn): + oneshot_warning.__init__(self,div=fn,fmt_args=[fn],reverse=True) -def encrypt_seed(data,key,desc='seed'): - return encrypt_data(data,key,desc=desc) + def get_hash_params(self,hash_preset): + if hash_preset in self.hash_presets: + return self.hash_presets[hash_preset] # N,r,p + else: # Shouldn't be here + die(3,f"{hash_preset}: invalid 'hash_preset' value") -def decrypt_seed(enc_seed,key,seed_id,key_id): - vmsg_r('Checking key...') - chk1 = make_chksum_8(key) - if key_id: - if not compare_chksums(key_id,'key ID',chk1,'computed'): + def sha256_rounds(self,s): + from hashlib import sha256 + for i in range(self.scramble_hash_rounds): + s = sha256(s).digest() + return s + + def scramble_seed(self,seed,scramble_key): + import hmac + step1 = hmac.digest(seed,scramble_key,'sha256') + if g.debug: + msg(f'Seed: {seed.hex()!r}\nScramble key: {scramble_key}\nScrambled seed: {step1.hex()}\n') + return self.sha256_rounds(step1) + + def encrypt_seed(self,data,key,desc='seed'): + return self.encrypt_data(data,key,desc=desc) + + def decrypt_seed(self,enc_seed,key,seed_id,key_id): + vmsg_r('Checking key...') + chk1 = make_chksum_8(key) + if key_id: + if not compare_chksums(key_id,'key ID',chk1,'computed'): + msg('Incorrect passphrase or hash preset') + return False + + dec_seed = self.decrypt_data(enc_seed,key,desc='seed') + chk2 = make_chksum_8(dec_seed) + if seed_id: + if compare_chksums(seed_id,'Seed ID',chk2,'decrypted seed'): + qmsg('Passphrase is OK') + else: + if not opt.debug: + msg_r('Checking key ID...') + if compare_chksums(key_id,'key ID',chk1,'computed'): + msg('Key ID is correct but decryption of seed failed') + else: + msg('Incorrect passphrase or hash preset') + vmsg('') + return False + + dmsg(f'Decrypted seed: {dec_seed.hex()}') + return dec_seed + + def encrypt_data(self,data,key,iv=aesctr_dfl_iv,desc='data',verify=True,silent=False): + from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes + from cryptography.hazmat.backends import default_backend + if not silent: + vmsg(f'Encrypting {desc}') + c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) + encryptor = c.encryptor() + enc_data = encryptor.update(data) + encryptor.finalize() + + if verify: + vmsg_r(f'Performing a test decryption of the {desc}...') + c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) + encryptor = c.encryptor() + dec_data = encryptor.update(enc_data) + encryptor.finalize() + if dec_data != data: + die(2,f'ERROR.\nDecrypted {desc} doesn’t match original {desc}') + if not silent: + vmsg('done') + + return enc_data + + def decrypt_data(self,enc_data,key,iv=aesctr_dfl_iv,desc='data'): + from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes + from cryptography.hazmat.backends import default_backend + vmsg_r(f'Decrypting {desc} with key...') + c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) + encryptor = c.encryptor() + return encryptor.update(enc_data) + encryptor.finalize() + + def scrypt_hash_passphrase(self,passwd,salt,hash_preset,buflen=32): + + # Buflen arg is for brainwallets only, which use this function to generate + # the seed directly. + ps = self.get_hash_params(hash_preset) + + if isinstance(passwd,str): + passwd = passwd.encode() + + def do_hashlib_scrypt(): + from hashlib import scrypt + return scrypt( + password = passwd, + salt = salt, + n = 2**ps.N, + r = ps.r, + p = ps.p, + maxmem = 0, + dklen = buflen ) + + def do_standalone_scrypt(): + import scrypt + return scrypt.hash( + password = passwd, + salt = salt, + N = 2**ps.N, + r = ps.r, + p = ps.p, + buflen = buflen ) + + if int(hash_preset) > 3: + msg_r('Hashing passphrase, please wait...') + + # hashlib.scrypt doesn't support N > 14 (hash preset > 3) + ret = ( + do_standalone_scrypt() if ps.N > 14 or g.force_standalone_scrypt_module else + do_hashlib_scrypt() ) + + if int(hash_preset) > 3: + msg_r('\b'*34 + ' '*34 + '\b'*34) + + return ret + + def make_key(self,passwd,salt,hash_preset,desc='encryption key',from_what='passphrase',verbose=False): + if opt.verbose or verbose: + msg_r(f"Generating {desc}{' from ' + from_what if from_what else ''}...") + key = self.scrypt_hash_passphrase(passwd,salt,hash_preset) + if opt.verbose or verbose: msg('done') + dmsg(f'Key: {key.hex()}') + return key + + def _get_random_data_from_user(self,uchars=None,desc='data'): + + if uchars is None: + uchars = opt.usr_randchars + + info1 = f""" + Now we're going to gather some additional input from the keyboard to further + randomize the random data {desc}. + + An encryption key will be created from this input, and the random data will + be encrypted using the key. The resulting data is guaranteed to be at least + as random as the original random data, so even if you type very predictably + no harm will be done. + + However, to gain the maximum benefit, try making your input as random as + possible. Type slowly and choose your symbols carefully. Try to use both + upper and lowercase letters as well as punctuation and numerals. The timings + between your keystrokes will also be used as a source of entropy, so be as + random as possible in your timing as well. + """ + info2 = f""" + Please type {uchars} symbols on your keyboard. What you type will not be displayed + on the screen. + """ + + msg(f'Enter {uchars} random symbols' if opt.quiet else + '\n' + fmt(info1,indent=' ') + + '\n' + fmt(info2) ) + + import time + from .term import get_char_raw + key_data = '' + time_data = [] + + for i in range(uchars): + key_data += get_char_raw(f'\rYou may begin typing. {uchars-i} symbols left: ') + time_data.append(time.time()) + + msg_r( '\r' if opt.quiet else f'\rThank you. That’s enough.{" "*18}\n\n' ) + + time_data = [f'{t:.22f}'.rstrip('0') for t in time_data] + + avg_prec = sum(len(t.split('.')[1]) for t in time_data) // len(time_data) + + if avg_prec < g.min_time_precision: + ymsg(f'WARNING: Avg. time precision of only {avg_prec} decimal points. User entropy quality is degraded!') + + ret = key_data + '\n' + '\n'.join(time_data) + + if g.debug: + msg(f'USER ENTROPY (user input + keystroke timings):\n{ret}') + + from .ui import line_input + line_input( 'User random data successfully acquired. Press ENTER to continue: ' ) + + return ret.encode() + + def get_random(self,length): + + os_rand = os.urandom(length) + assert len(os_rand) == length, f'OS random number generator returned {len(os_rand)} (!= {length}) bytes!' + + return self.add_user_random( + rand_bytes = os_rand, + desc = 'from your operating system' ) + + def add_user_random( + self, + rand_bytes, + desc, + urand = {'data':b'', 'counter':0} ): + + assert type(rand_bytes) == bytes, 'add_user_random_chk1' + + if opt.usr_randchars: + + if not urand['data']: + from hashlib import sha256 + urand['data'] = sha256(self._get_random_data_from_user(desc=desc)).digest() + + # counter protects against very evil rng that might repeatedly output the same data + urand['counter'] += 1 + + os_rand = os.urandom(8) + assert len(os_rand) == 8, f'OS random number generator returned {len(os_rand)} (!= 8) bytes!' + + import hmac + key = hmac.digest( + urand['data'], + os_rand + int.to_bytes(urand['counter'],8,'big'), + 'sha256' ) + + msg('Encrypting random data {} with ephemeral key #{}'.format( desc, urand['counter'] )) + + return self.encrypt_data( data=rand_bytes, key=key, desc=desc, verify=False, silent=True ) + else: + return rand_bytes + + def get_hash_preset_from_user( + self, + old_preset = g.dfl_hash_preset, + data_desc = 'data', + prompt = None ): + + prompt = prompt or ( + f'Enter hash preset for {data_desc},\n' + + f'or hit ENTER to accept the default value ({old_preset!r}): ' ) + + from .ui import line_input + while True: + ret = line_input( prompt ) + if ret: + if ret in self.hash_presets: + return ret + else: + msg('Invalid input. Valid choices are {}'.format(', '.join(self.hash_presets))) + else: + return old_preset + + def get_new_passphrase(self,data_desc,hash_preset,passwd_file,pw_desc='passphrase'): + message = f""" + You must choose a passphrase to encrypt your {data_desc} with. + A key will be generated from your passphrase using a hash preset of '{hash_preset}'. + Please note that no strength checking of passphrases is performed. + For an empty passphrase, just hit ENTER twice. + """ + if passwd_file: + from .fileutil import get_words_from_file + pw = ' '.join(get_words_from_file( + infile = passwd_file, + desc = f'{pw_desc} for {data_desc}', + quiet = self.pwfile_reuse_warning(passwd_file).warning_shown )) + else: + qmsg('\n'+fmt(message,indent=' ')) + from .ui import get_words_from_user + if opt.echo_passphrase: + pw = ' '.join(get_words_from_user( f'Enter {pw_desc} for {data_desc}: ' )) + else: + for i in range(g.passwd_max_tries): + pw = ' '.join(get_words_from_user( f'Enter {pw_desc} for {data_desc}: ' )) + pw_chk = ' '.join(get_words_from_user( f'Repeat {pw_desc}: ' )) + dmsg(f'Passphrases: [{pw}] [{pw_chk}]') + if pw == pw_chk: + vmsg('Passphrases match') + break + else: + msg('Passphrases do not match. Try again.') + else: + die(2,f'User failed to duplicate passphrase in {g.passwd_max_tries} attempts') + + if pw == '': + qmsg('WARNING: Empty passphrase') + + return pw + + def get_passphrase(self,data_desc,passwd_file,pw_desc='passphrase'): + if passwd_file: + from .fileutil import get_words_from_file + return ' '.join(get_words_from_file( + infile = passwd_file, + desc = f'{pw_desc} for {data_desc}', + quiet = self.pwfile_reuse_warning(passwd_file).warning_shown )) + else: + from .ui import get_words_from_user + return ' '.join(get_words_from_user( f'Enter {pw_desc} for {data_desc}: ' )) + + def mmgen_encrypt(self,data,desc='data',hash_preset=None): + salt = self.get_random(self.mmenc_salt_len) + iv = self.get_random(self.aesctr_iv_len) + nonce = self.get_random(self.mmenc_nonce_len) + hp = hash_preset or opt.hash_preset or self.get_hash_preset_from_user(data_desc=desc) + m = ('user-requested','default')[hp=='3'] + vmsg(f'Encrypting {desc}') + qmsg(f'Using {m} hash preset of {hp!r}') + passwd = self.get_new_passphrase( + data_desc = desc, + hash_preset = hp, + passwd_file = opt.passwd_file ) + key = self.make_key(passwd,salt,hp) + from hashlib import sha256 + enc_d = self.encrypt_data( sha256(nonce+data).digest() + nonce + data, key, iv, desc=desc ) + return salt+iv+enc_d + + def mmgen_decrypt(self,data,desc='data',hash_preset=None): + vmsg(f'Preparing to decrypt {desc}') + dstart = self.mmenc_salt_len + self.aesctr_iv_len + salt = data[:self.mmenc_salt_len] + iv = data[self.mmenc_salt_len:dstart] + enc_d = data[dstart:] + hp = hash_preset or opt.hash_preset or self.get_hash_preset_from_user(data_desc=desc) + m = ('user-requested','default')[hp=='3'] + qmsg(f'Using {m} hash preset of {hp!r}') + passwd = self.get_passphrase( + data_desc = desc, + passwd_file = opt.passwd_file ) + key = self.make_key(passwd,salt,hp) + dec_d = self.decrypt_data( enc_d, key, iv, desc ) + sha256_len = 32 + from hashlib import sha256 + if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest(): + vmsg('OK') + return dec_d[sha256_len+self.mmenc_nonce_len:] + else: msg('Incorrect passphrase or hash preset') return False - dec_seed = decrypt_data(enc_seed,key,desc='seed') - chk2 = make_chksum_8(dec_seed) - if seed_id: - if compare_chksums(seed_id,'Seed ID',chk2,'decrypted seed'): - qmsg('Passphrase is OK') - else: - if not opt.debug: - msg_r('Checking key ID...') - if compare_chksums(key_id,'key ID',chk1,'computed'): - msg('Key ID is correct but decryption of seed failed') - else: - msg('Incorrect passphrase or hash preset') - vmsg('') - return False -# else: -# qmsg(f'Generated IDs (Seed/Key): {chk2}/{chk1}') - - dmsg(f'Decrypted seed: {dec_seed.hex()}') - return dec_seed - -def encrypt_data(data,key,iv=aesctr_dfl_iv,desc='data',verify=True,silent=False): - from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes - from cryptography.hazmat.backends import default_backend - if not silent: - vmsg(f'Encrypting {desc}') - c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) - encryptor = c.encryptor() - enc_data = encryptor.update(data) + encryptor.finalize() - - if verify: - vmsg_r(f'Performing a test decryption of the {desc}...') - c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) - encryptor = c.encryptor() - dec_data = encryptor.update(enc_data) + encryptor.finalize() - if dec_data != data: - die(2,f'ERROR.\nDecrypted {desc} doesn’t match original {desc}') - if not silent: - vmsg('done') - - return enc_data - -def decrypt_data(enc_data,key,iv=aesctr_dfl_iv,desc='data'): - from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes - from cryptography.hazmat.backends import default_backend - vmsg_r(f'Decrypting {desc} with key...') - c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) - encryptor = c.encryptor() - return encryptor.update(enc_data) + encryptor.finalize() - -def scrypt_hash_passphrase(passwd,salt,hash_preset,buflen=32): - - # Buflen arg is for brainwallets only, which use this function to generate - # the seed directly. - ps = get_hash_params(hash_preset) - - if isinstance(passwd,str): - passwd = passwd.encode() - - def do_hashlib_scrypt(): - from hashlib import scrypt - return scrypt( - password = passwd, - salt = salt, - n = 2**ps.N, - r = ps.r, - p = ps.p, - maxmem = 0, - dklen = buflen ) - - def do_standalone_scrypt(): - import scrypt - return scrypt.hash( - password = passwd, - salt = salt, - N = 2**ps.N, - r = ps.r, - p = ps.p, - buflen = buflen ) - - if int(hash_preset) > 3: - msg_r('Hashing passphrase, please wait...') - - # hashlib.scrypt doesn't support N > 14 (hash preset > 3) - ret = ( - do_standalone_scrypt() if ps.N > 14 or g.force_standalone_scrypt_module else - do_hashlib_scrypt() ) - - if int(hash_preset) > 3: - msg_r('\b'*34 + ' '*34 + '\b'*34) - - return ret - -def make_key(passwd,salt,hash_preset,desc='encryption key',from_what='passphrase',verbose=False): - if opt.verbose or verbose: - msg_r(f"Generating {desc}{' from ' + from_what if from_what else ''}...") - key = scrypt_hash_passphrase(passwd,salt,hash_preset) - if opt.verbose or verbose: msg('done') - dmsg(f'Key: {key.hex()}') - return key - -def _get_random_data_from_user(uchars,desc): - info1 = f""" - Now we're going to gather some additional input from the keyboard to further - randomize the random data {desc}. - - An encryption key will be created from this input, and the random data will - be encrypted using the key. The resulting data is guaranteed to be at least - as random as the original random data, so even if you type very predictably - no harm will be done. - - However, to gain the maximum benefit, try making your input as random as - possible. Type slowly and choose your symbols carefully. Try to use both - upper and lowercase letters as well as punctuation and numerals. The timings - between your keystrokes will also be used as a source of entropy, so be as - random as possible in your timing as well. - """ - info2 = f""" - Please type {uchars} symbols on your keyboard. What you type will not be displayed - on the screen. - """ - - msg(f'Enter {uchars} random symbols' if opt.quiet else - '\n' + fmt(info1,indent=' ') + - '\n' + fmt(info2) ) - - import time - from .term import get_char_raw - key_data = '' - time_data = [] - - for i in range(uchars): - key_data += get_char_raw(f'\rYou may begin typing. {uchars-i} symbols left: ') - time_data.append(time.time()) - - msg_r( '\r' if opt.quiet else f'\rThank you. That’s enough.{" "*18}\n\n' ) - - time_data = [f'{t:.22f}'.rstrip('0') for t in time_data] - - avg_prec = sum(len(t.split('.')[1]) for t in time_data) // len(time_data) - - if avg_prec < g.min_time_precision: - ymsg(f'WARNING: Avg. time precision of only {avg_prec} decimal points. User entropy quality is degraded!') - - ret = key_data + '\n' + '\n'.join(time_data) - - if g.debug: - msg(f'USER ENTROPY (user input + keystroke timings):\n{ret}') - - from .ui import line_input - line_input('User random data successfully acquired. Press ENTER to continue: ') - - return ret.encode() - -def get_random(length): - - os_rand = os.urandom(length) - assert len(os_rand) == length, f'OS random number generator returned {len(os_rand)} (!= {length}) bytes!' - - return add_user_random( - rand_bytes = os_rand, - desc = 'from your operating system' ) - -def add_user_random( - rand_bytes, - desc, - urand = {'data':b'', 'counter':0} ): - - assert type(rand_bytes) == bytes, 'add_user_random_chk1' - - if opt.usr_randchars: - - if not urand['data']: - from hashlib import sha256 - urand['data'] = sha256(_get_random_data_from_user(opt.usr_randchars,desc)).digest() - - # counter protects against very evil rng that might repeatedly output the same data - urand['counter'] += 1 - - os_rand = os.urandom(8) - assert len(os_rand) == 8, f'OS random number generator returned {len(os_rand)} (!= 8) bytes!' - - import hmac - key = hmac.digest( - urand['data'], - os_rand + int.to_bytes(urand['counter'],8,'big'), - 'sha256' ) - - msg('Encrypting random data {} with ephemeral key #{}'.format( desc, urand['counter'] )) - - return encrypt_data( data=rand_bytes, key=key, desc=desc, verify=False, silent=True ) - else: - return rand_bytes - -def get_hash_preset_from_user( - old_preset = g.dfl_hash_preset, - data_desc = 'data', - prompt = None ): - - prompt = prompt or ( - f'Enter hash preset for {data_desc},\n' + - f'or hit ENTER to accept the default value ({old_preset!r}): ' ) - - from .ui import line_input - while True: - ret = line_input(prompt) - if ret: - if ret in hash_presets: - return ret - else: - msg('Invalid input. Valid choices are {}'.format(', '.join(hash_presets))) - else: - return old_preset - -def get_new_passphrase(data_desc,hash_preset,passwd_file,pw_desc='passphrase'): - message = f""" - You must choose a passphrase to encrypt your {data_desc} with. - A key will be generated from your passphrase using a hash preset of '{hash_preset}'. - Please note that no strength checking of passphrases is performed. - For an empty passphrase, just hit ENTER twice. - """ - if passwd_file: - from .fileutil import get_words_from_file - pw = ' '.join(get_words_from_file( - infile = passwd_file, - desc = f'{pw_desc} for {data_desc}', - quiet = pwfile_reuse_warning(passwd_file).warning_shown )) - else: - qmsg('\n'+fmt(message,indent=' ')) - from .ui import get_words_from_user - if opt.echo_passphrase: - pw = ' '.join(get_words_from_user(f'Enter {pw_desc} for {data_desc}: ')) - else: - for i in range(g.passwd_max_tries): - pw = ' '.join(get_words_from_user(f'Enter {pw_desc} for {data_desc}: ')) - pw_chk = ' '.join(get_words_from_user(f'Repeat {pw_desc}: ')) - dmsg(f'Passphrases: [{pw}] [{pw_chk}]') - if pw == pw_chk: - vmsg('Passphrases match') - break - else: - msg('Passphrases do not match. Try again.') - else: - die(2,f'User failed to duplicate passphrase in {g.passwd_max_tries} attempts') - - if pw == '': - qmsg('WARNING: Empty passphrase') - - return pw - -def get_passphrase(data_desc,passwd_file,pw_desc='passphrase'): - if passwd_file: - from .fileutil import get_words_from_file - return ' '.join(get_words_from_file( - infile = passwd_file, - desc = f'{pw_desc} for {data_desc}', - quiet = pwfile_reuse_warning(passwd_file).warning_shown )) - else: - from .ui import get_words_from_user - return ' '.join(get_words_from_user(f'Enter {pw_desc} for {data_desc}: ')) - -mmenc_salt_len = 32 -mmenc_nonce_len = 32 - -def mmgen_encrypt(data,desc='data',hash_preset=None): - salt = get_random(mmenc_salt_len) - iv = get_random(aesctr_iv_len) - nonce = get_random(mmenc_nonce_len) - hp = hash_preset or opt.hash_preset or get_hash_preset_from_user(data_desc=desc) - m = ('user-requested','default')[hp=='3'] - vmsg(f'Encrypting {desc}') - qmsg(f'Using {m} hash preset of {hp!r}') - passwd = get_new_passphrase( - data_desc = desc, - hash_preset = hp, - passwd_file = opt.passwd_file ) - key = make_key(passwd,salt,hp) - from hashlib import sha256 - enc_d = encrypt_data( sha256(nonce+data).digest() + nonce + data, key, iv, desc=desc ) - return salt+iv+enc_d - -def mmgen_decrypt(data,desc='data',hash_preset=None): - vmsg(f'Preparing to decrypt {desc}') - dstart = mmenc_salt_len + aesctr_iv_len - salt = data[:mmenc_salt_len] - iv = data[mmenc_salt_len:dstart] - enc_d = data[dstart:] - hp = hash_preset or opt.hash_preset or get_hash_preset_from_user(data_desc=desc) - m = ('user-requested','default')[hp=='3'] - qmsg(f'Using {m} hash preset of {hp!r}') - passwd = get_passphrase( - data_desc = desc, - passwd_file = opt.passwd_file ) - key = make_key(passwd,salt,hp) - dec_d = decrypt_data( enc_d, key, iv, desc ) - sha256_len = 32 - from hashlib import sha256 - if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest(): - vmsg('OK') - return dec_d[sha256_len+mmenc_nonce_len:] - else: - msg('Incorrect passphrase or hash preset') - return False - -def mmgen_decrypt_retry(d,desc='data'): - while True: - d_dec = mmgen_decrypt(d,desc) - if d_dec: return d_dec - msg('Trying again...') + def mmgen_decrypt_retry(self,d,desc='data'): + while True: + d_dec = self.mmgen_decrypt(d,desc) + if d_dec: + return d_dec + msg('Trying again...') diff --git a/mmgen/data/version b/mmgen/data/version index 2bf0a3fb..ece4cb50 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.3.dev40 +13.3.dev41 diff --git a/mmgen/fileutil.py b/mmgen/fileutil.py index 1a3d5d2f..856cfd67 100755 --- a/mmgen/fileutil.py +++ b/mmgen/fileutil.py @@ -322,13 +322,12 @@ def get_lines_from_file( def decrypt_file_maybe(): data = get_data_from_file( fn, desc=desc, binary=True, quiet=quiet, silent=silent ) - from .crypto import mmenc_ext - have_enc_ext = get_extension(fn) == mmenc_ext + from .crypto import Crypto + have_enc_ext = get_extension(fn) == Crypto.mmenc_ext if have_enc_ext or not is_utf8(data): m = ('Attempting to decrypt','Decrypting')[have_enc_ext] qmsg(f'{m} {desc} {fn!r}') - from .crypto import mmgen_decrypt_retry - data = mmgen_decrypt_retry(data,desc) + data = Crypto().mmgen_decrypt_retry(data,desc) return data lines = decrypt_file_maybe().decode().splitlines() diff --git a/mmgen/opts.py b/mmgen/opts.py index 89fc411a..2a424139 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -96,11 +96,11 @@ def die_on_incompatible_opts(incompat_list): def _show_hash_presets(): fs = ' {:<7} {:<6} {:<3} {}' from .util import msg - from .crypto import hash_presets + from .crypto import Crypto msg('Available parameters for scrypt.hash():') msg(fs.format('Preset','N','r','p')) - for i in sorted(hash_presets.keys()): - msg(fs.format(i,*hash_presets[i])) + for i in sorted(Crypto.hash_presets.keys()): + msg(fs.format(i,*Crypto.hash_presets[i])) msg('N = memory usage (power of two), p = iterations (rounds)') sys.exit(0) @@ -559,11 +559,10 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails opt_is_in_list(int(val),Seed.lens,desc) def chk_hash_preset(key,val,desc): - from .crypto import hash_presets - opt_is_in_list(val,list(hash_presets.keys()),desc) + from .crypto import Crypto + opt_is_in_list(val,list(Crypto.hash_presets.keys()),desc) def chk_brain_params(key,val,desc): - from .crypto import hash_presets a = val.split(',') if len(a) != 2: opt_display(key,val) @@ -571,7 +570,8 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails opt_is_int(a[0],'seed length '+desc) from .seed import Seed opt_is_in_list(int(a[0]),Seed.lens,'seed length '+desc) - opt_is_in_list(a[1],list(hash_presets.keys()),'hash preset '+desc) + from .crypto import Crypto + opt_is_in_list(a[1],list(Crypto.hash_presets.keys()),'hash preset '+desc) def chk_usr_randchars(key,val,desc): if val == 0: diff --git a/mmgen/passwdlist.py b/mmgen/passwdlist.py index 19e4fece..396c21e7 100755 --- a/mmgen/passwdlist.py +++ b/mmgen/passwdlist.py @@ -239,5 +239,5 @@ class PasswordList(AddrList): scramble_key = f'hex:{pwlen}:{self.pw_id_str}' self.dmsg_sc('str',scramble_key) - from .crypto import scramble_seed - return scramble_seed(seed,scramble_key.encode()) + from .crypto import Crypto + return Crypto().scramble_seed(seed,scramble_key.encode()) diff --git a/mmgen/seed.py b/mmgen/seed.py index 64be871d..8ab929ba 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -57,10 +57,10 @@ class SeedBase(MMGenObject): def __init__(self,seed_bin=None,nSubseeds=None): if not seed_bin: from .opts import opt - from .crypto import get_random + from .crypto import Crypto from hashlib import sha256 # Truncate random data for smaller seed lengths - seed_bin = sha256(get_random(1033)).digest()[:(opt.seed_len or self.dfl_len)//8] + seed_bin = sha256(Crypto().get_random(1033)).digest()[:(opt.seed_len or self.dfl_len)//8] elif len(seed_bin)*8 not in self.lens: die(3,f'{len(seed_bin)*8}: invalid seed bit length') diff --git a/mmgen/seedsplit.py b/mmgen/seedsplit.py index 49009529..c25639df 100755 --- a/mmgen/seedsplit.py +++ b/mmgen/seedsplit.py @@ -24,6 +24,7 @@ from .globalvars import g from .color import yellow from .obj import MMGenPWIDString,MMGenIdx from .subseed import * +from .crypto import Crypto class SeedShareIdx(MMGenIdx): max_val = 1024 @@ -209,7 +210,7 @@ class SeedShare(SeedShareBase,SubSeed): b':master:' + parent_list.master_share.idx.to_bytes(2,'big') ) - return scramble_seed(seed.data,scramble_key)[:seed.byte_len] + return Crypto().scramble_seed(seed.data,scramble_key)[:seed.byte_len] class SeedShareLast(SeedShareBase,SeedBase): @@ -261,14 +262,14 @@ class SeedShareMaster(SeedBase,SeedShareBase): seed = self.parent_list.parent_seed # field maximums: idx: 65535 (1024) scramble_key = b'master_share:' + self.idx.to_bytes(2,'big') + self.nonce.to_bytes(2,'big') - return scramble_seed(seed.data,scramble_key)[:seed.byte_len] + return Crypto().scramble_seed(seed.data,scramble_key)[:seed.byte_len] # Don't bother with avoiding seed ID collision here, as sid of derived seed is not used # by user as an identifier def make_derived_seed_bin(self,id_str,count): # field maximums: id_str: none (256 chars), count: 65535 (1024) scramble_key = id_str.encode() + b':' + count.to_bytes(2,'big') - return scramble_seed(self.data,scramble_key)[:self.byte_len] + return Crypto().scramble_seed(self.data,scramble_key)[:self.byte_len] def get_desc(self,ui=False): psid = self.parent_list.parent_seed.sid diff --git a/mmgen/subseed.py b/mmgen/subseed.py index b57c4551..22dafc86 100755 --- a/mmgen/subseed.py +++ b/mmgen/subseed.py @@ -24,7 +24,6 @@ from .color import green from .util import msg_r,msg,qmsg,die from .obj import MMGenRange,IndexedDict from .seed import * -from .crypto import scramble_seed class SubSeedIdxRange(MMGenRange): min_idx = 1 @@ -75,7 +74,8 @@ class SubSeed(SeedBase): short = { 'short': True, 'long': False }[length] # field maximums: idx: 4294967295 (1000000), nonce: 65535 (1000), short: 255 (1) scramble_key = idx.to_bytes(4,'big') + nonce.to_bytes(2,'big') + short.to_bytes(1,'big') - return scramble_seed(seed.data,scramble_key)[:16 if short else seed.byte_len] + from .crypto import Crypto + return Crypto().scramble_seed(seed.data,scramble_key)[:16 if short else seed.byte_len] class SubSeedList(MMGenObject): have_short = True diff --git a/mmgen/tool/coin.py b/mmgen/tool/coin.py index 873b858d..46062aaa 100755 --- a/mmgen/tool/coin.py +++ b/mmgen/tool/coin.py @@ -51,20 +51,20 @@ class tool_cmd(tool_cmd_base): def randwif(self): "generate a random private key in WIF format" - from ..crypto import get_random + from ..crypto import Crypto return PrivKey( self.proto, - get_random(32), + Crypto().get_random(32), pubkey_type = self.mmtype.pubkey_type, compressed = self.mmtype.compressed ).wif def randpair(self): "generate a random private key/address pair" gd = self._init_generators() - from ..crypto import get_random + from ..crypto import Crypto privkey = PrivKey( self.proto, - get_random(32), + Crypto().get_random(32), pubkey_type = self.mmtype.pubkey_type, compressed = self.mmtype.compressed ) return ( diff --git a/mmgen/tool/filecrypt.py b/mmgen/tool/filecrypt.py index 79efb499..51823fea 100755 --- a/mmgen/tool/filecrypt.py +++ b/mmgen/tool/filecrypt.py @@ -23,7 +23,7 @@ tool.filecrypt: File encryption/decryption routines for the 'mmgen-tool' utility import os from .common import tool_cmd_base -from ..crypto import mmgen_encrypt,mmgen_decrypt,mmenc_ext +from ..crypto import Crypto from ..fileutil import get_data_from_file,write_data_to_file class tool_cmd(tool_cmd_base): @@ -38,9 +38,9 @@ class tool_cmd(tool_cmd_base): def encrypt(self,infile:str,outfile='',hash_preset=''): "encrypt a file" data = get_data_from_file( infile, 'data for encryption', binary=True ) - enc_d = mmgen_encrypt( data, 'data', hash_preset ) + enc_d = Crypto().mmgen_encrypt( data, 'data', hash_preset ) if not outfile: - outfile = f'{os.path.basename(infile)}.{mmenc_ext}' + outfile = f'{os.path.basename(infile)}.{Crypto.mmenc_ext}' write_data_to_file( outfile, enc_d, 'encrypted data', binary=True ) return True @@ -48,14 +48,14 @@ class tool_cmd(tool_cmd_base): "decrypt a file" enc_d = get_data_from_file( infile, 'encrypted data', binary=True ) while True: - dec_d = mmgen_decrypt( enc_d, 'data', hash_preset ) + dec_d = Crypto().mmgen_decrypt( enc_d, 'data', hash_preset ) if dec_d: break msg('Trying again...') if not outfile: from ..util import remove_extension o = os.path.basename(infile) - outfile = remove_extension(o,mmenc_ext) + outfile = remove_extension(o,Crypto.mmenc_ext) if outfile == o: outfile += '.dec' write_data_to_file( outfile, dec_d, 'decrypted data', binary=True ) diff --git a/mmgen/tool/fileutil.py b/mmgen/tool/fileutil.py index b4dd24ce..fd328e8b 100755 --- a/mmgen/tool/fileutil.py +++ b/mmgen/tool/fileutil.py @@ -24,7 +24,7 @@ import os from .common import tool_cmd_base from ..util import msg,msg_r,qmsg,die,suf,make_full_path -from ..crypto import get_random,aesctr_iv_len +from ..crypto import Crypto class tool_cmd(tool_cmd_base): "file utilities" @@ -38,7 +38,7 @@ class tool_cmd(tool_cmd_base): from hashlib import sha256 from ..globalvars import g - ivsize,bsize,mod = ( aesctr_iv_len, 4096, 4096*8 ) + ivsize,bsize,mod = ( Crypto.aesctr_iv_len, 4096, 4096*8 ) n,carry = 0,b' '*ivsize flgs = os.O_RDONLY|os.O_BINARY if g.platform == 'win' else os.O_RDONLY f = os.open(filename,flgs) @@ -98,7 +98,7 @@ class tool_cmd(tool_cmd_base): from ..util2 import parse_bytespec def encrypt_worker(wid): - ctr_init_val = os.urandom( aesctr_iv_len ) + ctr_init_val = os.urandom( Crypto.aesctr_iv_len ) c = Cipher( algorithms.AES(key), modes.CTR(ctr_init_val), backend=default_backend() ) encryptor = c.encryptor() while True: @@ -115,7 +115,7 @@ class tool_cmd(tool_cmd_base): outfile = make_full_path( opt.outdir, outfile ) f = open(outfile,'wb') - key = get_random(32) + key = Crypto().get_random(32) q1,q2 = ( Queue(), Queue() ) for i in range(max(1,threads-2)): diff --git a/mmgen/tool/mnemonic.py b/mmgen/tool/mnemonic.py index f79af8e4..9c543317 100755 --- a/mmgen/tool/mnemonic.py +++ b/mmgen/tool/mnemonic.py @@ -71,8 +71,8 @@ class tool_cmd(tool_cmd_base): def _do_random_mn(self,nbytes:int,fmt:str): assert nbytes in (16,24,32), 'nbytes must be 16, 24 or 32' - from ..crypto import get_random - randbytes = get_random(nbytes) + from ..crypto import Crypto + randbytes = Crypto().get_random(nbytes) if fmt == 'xmrseed': randbytes = self._xmr_reduce(randbytes) from ..opts import opt diff --git a/mmgen/tool/util.py b/mmgen/tool/util.py index d4b53da2..9b7e6668 100755 --- a/mmgen/tool/util.py +++ b/mmgen/tool/util.py @@ -92,8 +92,8 @@ class tool_cmd(tool_cmd_base): def randhex(self, nbytes: 'number of bytes to output' = 32 ): "print 'n' bytes (default 32) of random data in hex format" - from ..crypto import get_random - return get_random( nbytes ).hex() + from ..crypto import Crypto + return Crypto().get_random( nbytes ).hex() def hexreverse(self,hexstr:'sstr'): "reverse bytes of a hexadecimal string" @@ -175,9 +175,9 @@ class tool_cmd(tool_cmd_base): nbytes: 'number of bytes to output' = 32, pad: 'pad output to this width' = 0 ): "generate random data (default: 32 bytes) and convert it to base 58" - from ..crypto import get_random from ..baseconv import baseconv - return baseconv('b58').frombytes( get_random(nbytes), pad=pad, tostr=True ) + from ..crypto import Crypto + return baseconv('b58').frombytes( Crypto().get_random(nbytes), pad=pad, tostr=True ) def bytestob58(self,infile:str,pad: 'pad output to this width' = 0): "convert bytes to base 58 (supply data via STDIN)" diff --git a/mmgen/wallet/brain.py b/mmgen/wallet/brain.py index e7e5e3ae..47da1c12 100755 --- a/mmgen/wallet/brain.py +++ b/mmgen/wallet/brain.py @@ -17,7 +17,6 @@ from ..util import msg,qmsg,qmsg_r from ..color import yellow from .enc import wallet from .seed import Seed -import mmgen.crypto as crypto class wallet(wallet): @@ -46,7 +45,7 @@ class wallet(wallet): bw_seed_len = opt.seed_len or Seed.dfl_len qmsg_r('Hashing brainwallet data. Please wait...') # Use buflen arg of scrypt.hash() to get seed of desired length - seed = crypto.scrypt_hash_passphrase( + seed = self.crypto.scrypt_hash_passphrase( self.brainpasswd.encode(), b'', d.hash_preset, diff --git a/mmgen/wallet/dieroll.py b/mmgen/wallet/dieroll.py index ec2c5328..9b1f24cc 100755 --- a/mmgen/wallet/dieroll.py +++ b/mmgen/wallet/dieroll.py @@ -56,8 +56,8 @@ class wallet(wallet): if self.interactive_input and opt.usr_randchars: from ..ui import keypress_confirm if keypress_confirm(self.user_entropy_prompt): - from ..crypto import add_user_random - seed_bytes = add_user_random( + from ..crypto import Crypto + seed_bytes = Crypto().add_user_random( rand_bytes = seed_bytes, desc = 'gathered from your die rolls' ) self.desc += ' plus user-supplied entropy' diff --git a/mmgen/wallet/enc.py b/mmgen/wallet/enc.py index 96d93bc5..f5b2f9cb 100755 --- a/mmgen/wallet/enc.py +++ b/mmgen/wallet/enc.py @@ -15,13 +15,15 @@ wallet.enc: encrypted wallet base class from ..globalvars import g from ..opts import opt from ..util import msg,qmsg,make_chksum_8 - -import mmgen.crypto as crypto - from .base import wallet class wallet(wallet): + def __init__(self,*args,**kwargs): + from mmgen.crypto import Crypto + self.crypto = Crypto() + return super().__init__(*args,**kwargs) + def _decrypt_retry(self): while True: if self._decrypt(): @@ -38,7 +40,7 @@ class wallet(wallet): ('',' '+add_desc)[bool(add_desc)], ('accept the default','reuse the old')[self.op=='pwchg_new'], old_preset ) - return crypto.get_hash_preset_from_user( old_preset=old_preset, prompt=prompt ) + return self.crypto.get_hash_preset_from_user( old_preset=old_preset, prompt=prompt ) def _get_hash_preset(self,add_desc=''): if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'): @@ -63,14 +65,14 @@ class wallet(wallet): self.ssdata.hash_preset = hp def _get_new_passphrase(self): - return crypto.get_new_passphrase( + return self.crypto.get_new_passphrase( data_desc = ('new ' if self.op in ('new','conv') else '') + self.desc, hash_preset = self.ssdata.hash_preset, passwd_file = self.passwd_file, pw_desc = ('new ' if self.op=='pwchg_new' else '') + 'passphrase' ) def _get_passphrase(self,add_desc=''): - return crypto.get_passphrase( + return self.crypto.get_passphrase( data_desc = self.desc + (f' {add_desc}' if add_desc else ''), passwd_file = self.passwd_file, pw_desc = ('old ' if self.op == 'pwchg_old' else '') + 'passphrase' ) @@ -92,7 +94,7 @@ class wallet(wallet): d.passwd = self._get_new_passphrase() from hashlib import sha256 - d.salt = sha256( crypto.get_random(128) ).digest()[:crypto.salt_len] - key = crypto.make_key( d.passwd, d.salt, d.hash_preset ) + d.salt = sha256( self.crypto.get_random(128) ).digest()[:self.crypto.salt_len] + key = self.crypto.make_key( d.passwd, d.salt, d.hash_preset ) d.key_id = make_chksum_8(key) - d.enc_seed = crypto.encrypt_seed( self.seed.data, key ) + d.enc_seed = self.crypto.encrypt_seed( self.seed.data, key ) diff --git a/mmgen/wallet/incog_base.py b/mmgen/wallet/incog_base.py index b3ebeb9f..c41a3535 100755 --- a/mmgen/wallet/incog_base.py +++ b/mmgen/wallet/incog_base.py @@ -17,7 +17,6 @@ from ..opts import opt from ..seed import Seed from ..util import msg,vmsg,qmsg,make_chksum_8 from .enc import wallet -import mmgen.crypto as crypto class wallet(wallet): @@ -39,9 +38,9 @@ class wallet(wallet): def _get_incog_data_len(self,seed_len): return ( - crypto.aesctr_iv_len - + crypto.salt_len - + (0 if opt.old_incog_fmt else crypto.hincog_chk_len) + self.crypto.aesctr_iv_len + + self.crypto.salt_len + + (0 if opt.old_incog_fmt else self.crypto.hincog_chk_len) + seed_len//8 ) def _incog_data_size_chk(self): @@ -67,6 +66,7 @@ class wallet(wallet): if opt.old_incog_fmt: die(1,'Writing old-format incognito wallets is unsupported') d = self.ssdata + crypto = self.crypto d.iv = crypto.get_random( crypto.aesctr_iv_len ) d.iv_id = self._make_iv_chksum(d.iv) @@ -101,7 +101,7 @@ class wallet(wallet): def _format(self): d = self.ssdata - self.fmt_data = d.iv + crypto.encrypt_data( + self.fmt_data = d.iv + self.crypto.encrypt_data( data = d.salt + d.enc_seed, key = d.wrapper_key, iv = d.iv, @@ -124,9 +124,9 @@ class wallet(wallet): return False d = self.ssdata - d.iv = self.fmt_data[0:crypto.aesctr_iv_len] + d.iv = self.fmt_data[0:self.crypto.aesctr_iv_len] d.incog_id = self._make_iv_chksum(d.iv) - d.enc_incog_data = self.fmt_data[crypto.aesctr_iv_len:] + d.enc_incog_data = self.fmt_data[self.crypto.aesctr_iv_len:] msg(f'Incog Wallet ID: {d.incog_id}') qmsg('Check this value against your records') vmsg('\n ' + self.msg['check_incog_id'].strip()+'\n') @@ -155,6 +155,7 @@ class wallet(wallet): d = self.ssdata self._get_hash_preset(add_desc=d.incog_id) d.passwd = self._get_passphrase(add_desc=d.incog_id) + crypto = self.crypto # IV is used BOTH to initialize counter and to salt password! wrapper_key = crypto.make_key( diff --git a/mmgen/wallet/mmgen.py b/mmgen/wallet/mmgen.py index cd053968..5c27c58e 100755 --- a/mmgen/wallet/mmgen.py +++ b/mmgen/wallet/mmgen.py @@ -21,8 +21,6 @@ from ..util import msg,qmsg,make_timestamp,make_chksum_6,split_into_cols,is_chks from ..obj import MMGenWalletLabel,get_obj from ..baseconv import baseconv -import mmgen.crypto as crypto - from .enc import wallet class wallet(wallet): @@ -92,7 +90,7 @@ class wallet(wallet): lines = ( d.label, '{} {} {} {} {}'.format( s.sid.lower(), d.key_id.lower(), s.bitlen, d.pw_status, d.timestamp ), - '{}: {} {} {}'.format( d.hash_preset, *crypto.get_hash_params(d.hash_preset) ), + '{}: {} {} {}'.format( d.hash_preset, *self.crypto.get_hash_params(d.hash_preset) ), '{} {}'.format( make_chksum_6(slt_fmt), split_into_cols(4,slt_fmt) ), '{} {}'.format( make_chksum_6(es_fmt), split_into_cols(4,es_fmt) ) ) @@ -140,7 +138,7 @@ class wallet(wallet): hash_params = tuple(map(int,hpdata[1:])) - if hash_params != crypto.get_hash_params(d.hash_preset): + if hash_params != self.crypto.get_hash_params(d.hash_preset): msg(f'Hash parameters {" ".join(hash_params)!r} don’t match hash preset {d.hash_preset!r}') return False @@ -172,8 +170,8 @@ class wallet(wallet): # Needed for multiple transactions with {}-txsign d.passwd = self._get_passphrase( add_desc = os.path.basename(self.infile.name) if opt.quiet else '' ) - key = crypto.make_key( d.passwd, d.salt, d.hash_preset ) - ret = crypto.decrypt_seed( d.enc_seed, key, d.seed_id, d.key_id ) + key = self.crypto.make_key( d.passwd, d.salt, d.hash_preset ) + ret = self.crypto.decrypt_seed( d.enc_seed, key, d.seed_id, d.key_id ) if ret: self.seed = Seed(ret) return True diff --git a/test/misc/get_passphrase.py b/test/misc/get_passphrase.py index 57ef0d87..e1460041 100755 --- a/test/misc/get_passphrase.py +++ b/test/misc/get_passphrase.py @@ -21,22 +21,24 @@ cmd_args = opts.init({ """ }}) -from mmgen.crypto import get_passphrase,get_new_passphrase,get_hash_preset_from_user from mmgen.wallet import Wallet def crypto(): desc = 'test data' - pw = get_new_passphrase(data_desc=desc,hash_preset=g.dfl_hash_preset,passwd_file=None) + from mmgen.crypto import Crypto + crypto = Crypto() + + pw = crypto.get_new_passphrase(data_desc=desc,hash_preset=g.dfl_hash_preset,passwd_file=None) msg(f'==> got new passphrase: [{pw}]\n') - pw = get_passphrase(data_desc=desc,passwd_file=None) + pw = crypto.get_passphrase(data_desc=desc,passwd_file=None) msg(f'==> got passphrase: [{pw}]\n') - hp = get_hash_preset_from_user(data_desc=desc) + hp = crypto.get_hash_preset_from_user(data_desc=desc) msg(f'==> got hash preset: [{hp}]') - hp = get_hash_preset_from_user(data_desc=desc) + hp = crypto.get_hash_preset_from_user(data_desc=desc) msg(f'==> got hash preset: [{hp}]') def seed(): diff --git a/test/misc/oneshot_warning.py b/test/misc/oneshot_warning.py index 4e0d69d2..727f3581 100755 --- a/test/misc/oneshot_warning.py +++ b/test/misc/oneshot_warning.py @@ -30,11 +30,11 @@ class wg(oneshot_warning_group): for i in (1,2,3): - from mmgen.crypto import pwfile_reuse_warning + from mmgen.crypto import Crypto msg('\npw') for k in ('A','B'): - ret = pwfile_reuse_warning(k).warning_shown + ret = Crypto.pwfile_reuse_warning(k).warning_shown assert ret == (i != 1), 'warning_shown incorrect' msg('wg1') diff --git a/test/misc/term.py b/test/misc/term.py index b2ca384b..77a2f49f 100755 --- a/test/misc/term.py +++ b/test/misc/term.py @@ -144,8 +144,8 @@ def _tt_get_char(raw=False,one_char=False,immed_chars=''): def tt_urand(): cmsg('Testing _get_random_data_from_user():') - from mmgen.crypto import _get_random_data_from_user - ret = _get_random_data_from_user(10,desc='data').decode() + from mmgen.crypto import Crypto + ret = Crypto()._get_random_data_from_user(uchars=10,desc='data').decode() msg(f'USER ENTROPY (user input + keystroke timings):\n\n{fmt(ret," ")}') times = ret.splitlines()[1:] avg_prec = sum(len(t.split('.')[1]) for t in times) // len(times) diff --git a/test/unit_tests_d/ut_scrypt.py b/test/unit_tests_d/ut_scrypt.py index be5e365e..23c4073b 100755 --- a/test/unit_tests_d/ut_scrypt.py +++ b/test/unit_tests_d/ut_scrypt.py @@ -13,7 +13,9 @@ class unit_test(object): msg_r('Testing password hashing...') qmsg('') - from mmgen.crypto import scrypt_hash_passphrase,hash_presets + + from mmgen.crypto import Crypto + crypto = Crypto() salt = bytes.fromhex('f00f' * 16) @@ -37,7 +39,7 @@ class unit_test(object): omsg_r('.') else: msg_r(f'\n password {pw_disp:9} ') - ret = scrypt_hash_passphrase(pw,salt,'1').hex() + ret = crypto.scrypt_hash_passphrase(pw,salt,'1').hex() assert ret == res, ret def test_presets(do_presets): @@ -48,9 +50,9 @@ class unit_test(object): if opt.quiet: omsg_r('.') else: - msg_r(f'\n {hp!r:3}: {hash_presets[hp]!r:12} ') + msg_r(f'\n {hp!r:3}: {crypto.hash_presets[hp]!r:12} ') st = time.time() - ret = scrypt_hash_passphrase(pw,salt,hp).hex() + ret = crypto.scrypt_hash_passphrase(pw,salt,hp).hex() t = time.time() - st vmsg('' if g.test_suite_deterministic else f' {t:0.4f} secs') assert ret == res, ret