globalvars.py: move crypto constants to crypto.py

This commit is contained in:
The MMGen Project 2022-01-22 14:26:15 +00:00
commit 0ef7de1886
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
8 changed files with 86 additions and 77 deletions

View file

@ -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:]

View file

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

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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)

View file

@ -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