|
@@ -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
|
|
|
-
|
|
|
-# 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),
|
|
|
-}
|
|
|
-
|
|
|
-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 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):
|
|
|
- from hashlib import sha256
|
|
|
- for i in range(n):
|
|
|
- s = sha256(s).digest()
|
|
|
- return s
|
|
|
-
|
|
|
-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 )
|
|
|
-
|
|
|
-def encrypt_seed(data,key,desc='seed'):
|
|
|
- return encrypt_data(data,key,desc=desc)
|
|
|
-
|
|
|
-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'):
|
|
|
- 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}...')
|
|
|
+class Crypto:
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+ mmenc_salt_len = 32
|
|
|
+ mmenc_nonce_len = 32
|
|
|
+
|
|
|
+ # 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),
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 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 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()
|
|
|
- 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)
|
|
|
+ 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.
|
|
|
+ """
|
|
|
|
|
|
- return ret
|
|
|
+ msg(f'Enter {uchars} random symbols' if opt.quiet else
|
|
|
+ '\n' + fmt(info1,indent=' ') +
|
|
|
+ '\n' + fmt(info2) )
|
|
|
|
|
|
-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
|
|
|
+ import time
|
|
|
+ from .term import get_char_raw
|
|
|
+ key_data = ''
|
|
|
+ time_data = []
|
|
|
|
|
|
-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}.
|
|
|
+ for i in range(uchars):
|
|
|
+ key_data += get_char_raw(f'\rYou may begin typing. {uchars-i} symbols left: ')
|
|
|
+ time_data.append(time.time())
|
|
|
|
|
|
- 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.
|
|
|
+ msg_r( '\r' if opt.quiet else f'\rThank you. That’s enough.{" "*18}\n\n' )
|
|
|
|
|
|
- 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.
|
|
|
- """
|
|
|
+ time_data = [f'{t:.22f}'.rstrip('0') for t in time_data]
|
|
|
|
|
|
- msg(f'Enter {uchars} random symbols' if opt.quiet else
|
|
|
- '\n' + fmt(info1,indent=' ') +
|
|
|
- '\n' + fmt(info2) )
|
|
|
+ avg_prec = sum(len(t.split('.')[1]) for t in time_data) // len(time_data)
|
|
|
|
|
|
- import time
|
|
|
- from .term import get_char_raw
|
|
|
- key_data = ''
|
|
|
- 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!')
|
|
|
|
|
|
- for i in range(uchars):
|
|
|
- key_data += get_char_raw(f'\rYou may begin typing. {uchars-i} symbols left: ')
|
|
|
- time_data.append(time.time())
|
|
|
+ ret = key_data + '\n' + '\n'.join(time_data)
|
|
|
|
|
|
- msg_r( '\r' if opt.quiet else f'\rThank you. That’s enough.{" "*18}\n\n' )
|
|
|
+ if g.debug:
|
|
|
+ msg(f'USER ENTROPY (user input + keystroke timings):\n{ret}')
|
|
|
|
|
|
- time_data = [f'{t:.22f}'.rstrip('0') for t in time_data]
|
|
|
+ from .ui import line_input
|
|
|
+ line_input( 'User random data successfully acquired. Press ENTER to continue: ' )
|
|
|
|
|
|
- avg_prec = sum(len(t.split('.')[1]) for t in time_data) // len(time_data)
|
|
|
+ return ret.encode()
|
|
|
|
|
|
- if avg_prec < g.min_time_precision:
|
|
|
- ymsg(f'WARNING: Avg. time precision of only {avg_prec} decimal points. User entropy quality is degraded!')
|
|
|
+ def get_random(self,length):
|
|
|
|
|
|
- ret = key_data + '\n' + '\n'.join(time_data)
|
|
|
+ os_rand = os.urandom(length)
|
|
|
+ assert len(os_rand) == length, f'OS random number generator returned {len(os_rand)} (!= {length}) bytes!'
|
|
|
|
|
|
- if g.debug:
|
|
|
- msg(f'USER ENTROPY (user input + keystroke timings):\n{ret}')
|
|
|
+ return self.add_user_random(
|
|
|
+ rand_bytes = os_rand,
|
|
|
+ desc = 'from your operating system' )
|
|
|
|
|
|
- from .ui import line_input
|
|
|
- line_input('User random data successfully acquired. Press ENTER to continue: ')
|
|
|
+ def add_user_random(
|
|
|
+ self,
|
|
|
+ rand_bytes,
|
|
|
+ desc,
|
|
|
+ urand = {'data':b'', 'counter':0} ):
|
|
|
|
|
|
- return ret.encode()
|
|
|
+ assert type(rand_bytes) == bytes, 'add_user_random_chk1'
|
|
|
|
|
|
-def get_random(length):
|
|
|
+ if opt.usr_randchars:
|
|
|
|
|
|
- os_rand = os.urandom(length)
|
|
|
- assert len(os_rand) == length, f'OS random number generator returned {len(os_rand)} (!= {length}) bytes!'
|
|
|
+ if not urand['data']:
|
|
|
+ from hashlib import sha256
|
|
|
+ urand['data'] = sha256(self._get_random_data_from_user(desc=desc)).digest()
|
|
|
|
|
|
- return add_user_random(
|
|
|
- rand_bytes = os_rand,
|
|
|
- desc = 'from your operating system' )
|
|
|
+ # counter protects against very evil rng that might repeatedly output the same data
|
|
|
+ urand['counter'] += 1
|
|
|
|
|
|
-def add_user_random(
|
|
|
- rand_bytes,
|
|
|
- desc,
|
|
|
- urand = {'data':b'', 'counter':0} ):
|
|
|
+ os_rand = os.urandom(8)
|
|
|
+ assert len(os_rand) == 8, f'OS random number generator returned {len(os_rand)} (!= 8) bytes!'
|
|
|
|
|
|
- assert type(rand_bytes) == bytes, 'add_user_random_chk1'
|
|
|
+ import hmac
|
|
|
+ key = hmac.digest(
|
|
|
+ urand['data'],
|
|
|
+ os_rand + int.to_bytes(urand['counter'],8,'big'),
|
|
|
+ 'sha256' )
|
|
|
|
|
|
- if opt.usr_randchars:
|
|
|
+ msg('Encrypting random data {} with ephemeral key #{}'.format( desc, urand['counter'] ))
|
|
|
|
|
|
- if not urand['data']:
|
|
|
- from hashlib import sha256
|
|
|
- urand['data'] = sha256(_get_random_data_from_user(opt.usr_randchars,desc)).digest()
|
|
|
+ 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')
|
|
|
|
|
|
- # counter protects against very evil rng that might repeatedly output the same data
|
|
|
- urand['counter'] += 1
|
|
|
+ if pw == '':
|
|
|
+ qmsg('WARNING: Empty passphrase')
|
|
|
|
|
|
- os_rand = os.urandom(8)
|
|
|
- assert len(os_rand) == 8, f'OS random number generator returned {len(os_rand)} (!= 8) bytes!'
|
|
|
+ return pw
|
|
|
|
|
|
- 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)))
|
|
|
+ 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:
|
|
|
- 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}: '))
|
|
|
+ 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:
|
|
|
- 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...')
|
|
|
+ msg('Incorrect passphrase or hash preset')
|
|
|
+ return False
|
|
|
+
|
|
|
+ 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...')
|