Browse Source

globalvars.py: move crypto constants to crypto.py

The MMGen Project 3 years ago
parent
commit
0ef7de1886
8 changed files with 86 additions and 77 deletions
  1. 33 7
      mmgen/crypto.py
  2. 1 1
      mmgen/data/version
  3. 0 20
      mmgen/globalvars.py
  4. 7 4
      mmgen/opts.py
  5. 3 5
      mmgen/tool.py
  6. 0 6
      mmgen/util.py
  7. 40 32
      mmgen/wallet.py
  8. 2 2
      test/unit_tests_d/ut_scrypt.py

+ 33 - 7
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:]

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-13.1.dev007
+13.1.dev008

+ 0 - 20
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

+ 7 - 4
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:

+ 3 - 5
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:

+ 0 - 6
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:

+ 40 - 32
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)

+ 2 - 2
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