From 0ef7de1886d2e95de47bb34cf78069f867be4c36 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sat, 22 Jan 2022 14:26:15 +0000 Subject: [PATCH] globalvars.py: move crypto constants to crypto.py --- mmgen/crypto.py | 40 +++++++++++++++---- mmgen/data/version | 2 +- mmgen/globalvars.py | 20 ---------- mmgen/opts.py | 11 ++++-- mmgen/tool.py | 8 ++-- mmgen/util.py | 6 --- mmgen/wallet.py | 72 +++++++++++++++++++--------------- test/unit_tests_d/ut_scrypt.py | 4 +- 8 files changed, 86 insertions(+), 77 deletions(-) diff --git a/mmgen/crypto.py b/mmgen/crypto.py index 37ecd143..37525340 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -44,6 +44,32 @@ from .util import ( ) 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 + +# 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 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") def sha256_rounds(s,n): for i in range(n): @@ -55,7 +81,7 @@ def scramble_seed(seed,scramble_key): 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,g.scramble_hash_rounds) + return sha256_rounds( step1, scramble_hash_rounds ) def encrypt_seed(seed,key): return encrypt_data(seed,key,desc='seed') @@ -88,7 +114,7 @@ def decrypt_seed(enc_seed,key,seed_id,key_id): dmsg(f'Decrypted seed: {dec_seed.hex()}') return dec_seed -def encrypt_data(data,key,iv=g.aesctr_dfl_iv,desc='data',verify=True): +def encrypt_data(data,key,iv=aesctr_dfl_iv,desc='data',verify=True): vmsg(f'Encrypting {desc}') c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) encryptor = c.encryptor() @@ -105,7 +131,7 @@ def encrypt_data(data,key,iv=g.aesctr_dfl_iv,desc='data',verify=True): return enc_data -def decrypt_data(enc_data,key,iv=g.aesctr_dfl_iv,desc='data'): +def decrypt_data(enc_data,key,iv=aesctr_dfl_iv,desc='data'): vmsg_r(f'Decrypting {desc} with key...') c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend()) encryptor = c.encryptor() @@ -245,10 +271,10 @@ def get_hash_preset_from_user( while True: ret = line_input(prompt) if ret: - if ret in g.hash_presets: + if ret in hash_presets: return ret else: - msg('Invalid input. Valid choices are {}'.format(', '.join(g.hash_presets))) + msg('Invalid input. Valid choices are {}'.format(', '.join(hash_presets))) else: return hash_preset @@ -302,7 +328,7 @@ mmenc_nonce_len = 32 def mmgen_encrypt(data,desc='data',hash_preset=None): salt = get_random(mmenc_salt_len) - iv = get_random(g.aesctr_iv_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'] @@ -318,7 +344,7 @@ def mmgen_encrypt(data,desc='data',hash_preset=None): def mmgen_decrypt(data,desc='data',hash_preset=None): vmsg(f'Preparing to decrypt {desc}') - dstart = mmenc_salt_len + g.aesctr_iv_len + dstart = mmenc_salt_len + aesctr_iv_len salt = data[:mmenc_salt_len] iv = data[mmenc_salt_len:dstart] enc_d = data[dstart:] diff --git a/mmgen/data/version b/mmgen/data/version index 28601de0..00986ebc 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.1.dev007 +13.1.dev008 diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index f39ae1eb..81b8b2df 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -301,27 +301,7 @@ class GlobalContext(Lockable): max_urandchars = 80 min_urandchars = 10 - 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 - force_standalone_scrypt_module = False - # 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), - } if os.getenv('MMGEN_TEST_SUITE'): err_disp_timeout = 0.1 diff --git a/mmgen/opts.py b/mmgen/opts.py index 77d83e96..88e8d089 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -84,10 +84,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 msg('Available parameters for scrypt.hash():') msg(fs.format('Preset','N','r','p')) - for i in sorted(g.hash_presets.keys()): - msg(fs.format(i,*g.hash_presets[i])) + for i in sorted(hash_presets.keys()): + msg(fs.format(i,*hash_presets[i])) msg('N = memory usage (power of two), p = iterations (rounds)') sys.exit(0) @@ -554,17 +555,19 @@ 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): - opt_is_in_list(val,list(g.hash_presets.keys()),desc) + from .crypto import hash_presets + opt_is_in_list(val,list(hash_presets.keys()),desc) def chk_brain_params(key,val,desc): from .seed import Seed + from .crypto import hash_presets a = val.split(',') if len(a) != 2: opt_display(key,val) raise UserOptError('Option requires two comma-separated arguments') opt_is_int(a[0],'seed length '+desc) opt_is_in_list(int(a[0]),Seed.lens,'seed length '+desc) - opt_is_in_list(a[1],list(g.hash_presets.keys()),'hash preset '+desc) + opt_is_in_list(a[1],list(hash_presets.keys()),'hash preset '+desc) def chk_usr_randchars(key,val,desc): if val == 0: diff --git a/mmgen/tool.py b/mmgen/tool.py index 3caf71f7..f91f9939 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -23,7 +23,7 @@ tool.py: Routines for the 'mmgen-tool' utility from .common import * from .protocol import hash160 from .fileutil import get_seed_file,get_data_from_file,write_data_to_file -from .crypto import get_random +from .crypto import get_random,aesctr_iv_len,mmgen_encrypt,mmgen_decrypt,mmenc_ext from .key import PrivKey from .subseed import SubSeedList from .seedsplit import MasterShareIdx @@ -730,7 +730,6 @@ class MMGenToolCmdFileCrypt(MMGenToolCmds): def encrypt(self,infile:str,outfile='',hash_preset=''): "encrypt a file" data = get_data_from_file(infile,'data for encryption',binary=True) - from .crypto import mmgen_encrypt,mmenc_ext enc_d = mmgen_encrypt(data,'data',hash_preset) if not outfile: outfile = f'{os.path.basename(infile)}.{mmenc_ext}' @@ -740,7 +739,6 @@ class MMGenToolCmdFileCrypt(MMGenToolCmds): def decrypt(self,infile:str,outfile='',hash_preset=''): "decrypt a file" enc_d = get_data_from_file(infile,'encrypted data',binary=True) - from .crypto import mmgen_decrypt,mmenc_ext while True: dec_d = mmgen_decrypt(enc_d,'data',hash_preset) if dec_d: break @@ -757,7 +755,7 @@ class MMGenToolCmdFileUtil(MMGenToolCmds): def find_incog_data(self,filename:str,incog_id:str,keep_searching=False): "Use an Incog ID to find hidden incognito wallet data" - ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8 + ivsize,bsize,mod = ( 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) @@ -792,7 +790,7 @@ class MMGenToolCmdFileUtil(MMGenToolCmds): from cryptography.hazmat.backends import default_backend def encrypt_worker(wid): - ctr_init_val = os.urandom(g.aesctr_iv_len) + ctr_init_val = os.urandom( aesctr_iv_len ) c = Cipher(algorithms.AES(key),modes.CTR(ctr_init_val),backend=default_backend()) encryptor = c.encryptor() while True: diff --git a/mmgen/util.py b/mmgen/util.py index 4da596df..d4aba236 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -417,12 +417,6 @@ def strip_comment(line): def strip_comments(lines): return [m for m in [strip_comment(l) for l in lines] if m != ''] -def get_hash_params(hash_preset): - if hash_preset in g.hash_presets: - return g.hash_presets[hash_preset] # N,r,p - else: # Shouldn't be here - die(3,f"{hash_preset}: invalid 'hash_preset' value") - def compare_chksums(chk1,desc1,chk2,desc2,hdr='',die_on_fail=False,verbose=False): if not chk1 == chk2: diff --git a/mmgen/wallet.py b/mmgen/wallet.py index cfef364e..7d99dce8 100755 --- a/mmgen/wallet.py +++ b/mmgen/wallet.py @@ -24,9 +24,9 @@ import os from .common import * from .obj import * -from .crypto import * from .baseconv import * from .seed import Seed +import mmgen.crypto as crypto def check_usr_seed_len(seed_len): if opt.seed_len and opt.seed_len != seed_len: @@ -298,8 +298,7 @@ class WalletEnc(Wallet): ('',' '+add_desc)[bool(add_desc)], ('accept the default','reuse the old')[self.op=='pwchg_new'], hp ) - from .crypto import get_hash_preset_from_user - return get_hash_preset_from_user( hash_preset=hp, prompt=prompt ) + return crypto.get_hash_preset_from_user( hash_preset=hp, prompt=prompt ) def _get_hash_preset(self,add_desc=''): if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'): @@ -322,8 +321,7 @@ class WalletEnc(Wallet): self.ssdata.hash_preset = hp def _get_new_passphrase(self): - from .crypto import get_new_passphrase - self.ssdata.passwd = get_new_passphrase( + self.ssdata.passwd = 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, @@ -331,8 +329,7 @@ class WalletEnc(Wallet): return self.ssdata.passwd def _get_passphrase(self,add_desc=''): - from .crypto import get_passphrase - self.ssdata.passwd = get_passphrase( + self.ssdata.passwd = 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' ) @@ -353,10 +350,10 @@ class WalletEnc(Wallet): else: self._get_new_passphrase() - d.salt = sha256(get_random(128)).digest()[:g.salt_len] - key = make_key(d.passwd, d.salt, d.hash_preset) + d.salt = sha256( crypto.get_random(128) ).digest()[:crypto.salt_len] + key = crypto.make_key( d.passwd, d.salt, d.hash_preset ) d.key_id = make_chksum_8(key) - d.enc_seed = encrypt_seed(self.seed.data,key) + d.enc_seed = crypto.encrypt_seed( self.seed.data, key ) class Mnemonic(WalletUnenc): @@ -536,7 +533,7 @@ class DieRollSeedFile(WalletUnenc): if self.interactive_input and opt.usr_randchars: if keypress_confirm(self.user_entropy_prompt): - seed_bytes = add_user_random( + seed_bytes = crypto.add_user_random( rand_bytes = seed_bytes, desc = 'gathered from your die rolls' ) self.desc += ' plus user-supplied entropy' @@ -741,7 +738,7 @@ class MMGenWallet(WalletEnc): lines = ( d.label, '{} {} {} {} {}'.format( s.sid.lower(), d.key_id.lower(), s.bitlen, d.pw_status, d.timestamp ), - '{}: {} {} {}'.format( d.hash_preset, *get_hash_params(d.hash_preset) ), + '{}: {} {} {}'.format( d.hash_preset, *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) ) ) @@ -789,7 +786,7 @@ class MMGenWallet(WalletEnc): hash_params = tuple(map(int,hpdata[1:])) - if hash_params != get_hash_params(d.hash_preset): + if hash_params != 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 @@ -821,8 +818,8 @@ class MMGenWallet(WalletEnc): # Needed for multiple transactions with {}-txsign self._get_passphrase( add_desc = os.path.basename(self.infile.name) if opt.quiet else '' ) - key = make_key(d.passwd, d.salt, d.hash_preset) - ret = decrypt_seed(d.enc_seed, key, d.seed_id, d.key_id) + 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 ) if ret: self.seed = Seed(ret) return True @@ -872,7 +869,7 @@ class Brainwallet(WalletEnc): 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 = scrypt_hash_passphrase( + seed = crypto.scrypt_hash_passphrase( self.brainpasswd.encode(), b'', d.hash_preset, @@ -914,8 +911,11 @@ to exit and re-run the program with the '--old-incog-fmt' option. def _make_iv_chksum(self,s): return sha256(s).hexdigest()[:8].upper() def _get_incog_data_len(self,seed_len): - e = (g.hincog_chk_len,0)[bool(opt.old_incog_fmt)] - return g.aesctr_iv_len + g.salt_len + e + seed_len//8 + return ( + crypto.aesctr_iv_len + + crypto.salt_len + + (0 if opt.old_incog_fmt else crypto.hincog_chk_len) + + seed_len//8 ) def _incog_data_size_chk(self): # valid sizes: 56, 64, 72 @@ -941,25 +941,33 @@ to exit and re-run the program with the '--old-incog-fmt' option. die(1,'Writing old-format incog wallets is unsupported') d = self.ssdata # IV is used BOTH to initialize counter and to salt password! - d.iv = get_random(g.aesctr_iv_len) + d.iv = crypto.get_random( crypto.aesctr_iv_len ) d.iv_id = self._make_iv_chksum(d.iv) msg(f'New Incog Wallet ID: {d.iv_id}') qmsg('Make a record of this value') vmsg(self.msg['record_incog_id']) - d.salt = get_random(g.salt_len) - key = make_key(d.passwd, d.salt, d.hash_preset, 'incog wallet key') + d.salt = crypto.get_random( crypto.salt_len ) + key = crypto.make_key( d.passwd, d.salt, d.hash_preset, 'incog wallet key' ) chk = sha256(self.seed.data).digest()[:8] - d.enc_seed = encrypt_data(chk+self.seed.data, key, g.aesctr_dfl_iv, 'seed') + d.enc_seed = crypto.encrypt_data( + chk + self.seed.data, + key, + crypto.aesctr_dfl_iv, + 'seed' ) - d.wrapper_key = make_key(d.passwd, d.iv, d.hash_preset, 'incog wrapper key') + d.wrapper_key = crypto.make_key( d.passwd, d.iv, d.hash_preset, 'incog wrapper key' ) d.key_id = make_chksum_8(d.wrapper_key) vmsg(f'Key ID: {d.key_id}') d.target_data_len = self._get_incog_data_len(self.seed.bitlen) def _format(self): d = self.ssdata - self.fmt_data = d.iv + encrypt_data(d.salt+d.enc_seed, d.wrapper_key, d.iv, self.desc) + self.fmt_data = d.iv + crypto.encrypt_data( + d.salt + d.enc_seed, + d.wrapper_key, + d.iv, + self.desc ) def _filename(self): s = self.seed @@ -979,9 +987,9 @@ to exit and re-run the program with the '--old-incog-fmt' option. return False d = self.ssdata - d.iv = self.fmt_data[0:g.aesctr_iv_len] + d.iv = self.fmt_data[0:crypto.aesctr_iv_len] d.incog_id = self._make_iv_chksum(d.iv) - d.enc_incog_data = self.fmt_data[g.aesctr_iv_len:] + d.enc_incog_data = self.fmt_data[crypto.aesctr_iv_len:] msg(f'Incog Wallet ID: {d.incog_id}') qmsg('Check this value against your records') vmsg(self.msg['check_incog_id']) @@ -1010,19 +1018,19 @@ to exit and re-run the program with the '--old-incog-fmt' option. self._get_passphrase(add_desc=d.incog_id) # IV is used BOTH to initialize counter and to salt password! - key = make_key(d.passwd, d.iv, d.hash_preset, 'wrapper key') - dd = decrypt_data(d.enc_incog_data, key, d.iv, 'incog data') + key = crypto.make_key( d.passwd, d.iv, d.hash_preset, 'wrapper key' ) + dd = crypto.decrypt_data( d.enc_incog_data, key, d.iv, 'incog data' ) - d.salt = dd[0:g.salt_len] - d.enc_seed = dd[g.salt_len:] + d.salt = dd[0:crypto.salt_len] + d.enc_seed = dd[crypto.salt_len:] - key = make_key(d.passwd, d.salt, d.hash_preset, 'main key') + key = crypto.make_key( d.passwd, d.salt, d.hash_preset, 'main key' ) qmsg(f'Key ID: {make_chksum_8(key)}') verify_seed = getattr(self,'_verify_seed_'+ ('newfmt','oldfmt')[bool(opt.old_incog_fmt)]) - seed = verify_seed(decrypt_seed(d.enc_seed, key, '', '')) + seed = verify_seed( crypto.decrypt_seed(d.enc_seed, key, '', '') ) if seed: self.seed = Seed(seed) diff --git a/test/unit_tests_d/ut_scrypt.py b/test/unit_tests_d/ut_scrypt.py index 171f5b29..30b27c6b 100755 --- a/test/unit_tests_d/ut_scrypt.py +++ b/test/unit_tests_d/ut_scrypt.py @@ -12,7 +12,7 @@ class unit_test(object): msg_r('Testing password hashing...') qmsg('') - from mmgen.crypto import scrypt_hash_passphrase + from mmgen.crypto import scrypt_hash_passphrase,hash_presets salt = bytes.fromhex('f00f' * 16) @@ -48,7 +48,7 @@ class unit_test(object): if opt.quiet: omsg_r('.') else: - msg_r(f'\n {hp!r:3}: {g.hash_presets[hp]!r:12} ') + msg_r(f'\n {hp!r:3}: {hash_presets[hp]!r:12} ') st = time.time() ret = scrypt_hash_passphrase(pw,salt,hp).hex() t = time.time() - st