crypto.py: reimplement as class
This commit is contained in:
parent
795b653c6d
commit
19f0730913
24 changed files with 430 additions and 419 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
716
mmgen/crypto.py
716
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...')
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
13.3.dev40
|
||||
13.3.dev41
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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)):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue