crypto.py: reimplement AES routines using the cryptography library

- the pycrypto library is now no longer used by MMGen
- cryptography initializes the counter with bytes instead of an int,
  leading to a small API change
This commit is contained in:
The MMGen Project 2019-03-21 12:23:13 +00:00
commit 8a3b9216f6
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 25 additions and 34 deletions

View file

@ -20,8 +20,9 @@
crypto.py: Cryptographic and related routines for the MMGen suite
"""
from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes
from cryptography.hazmat.backends import default_backend
from hashlib import sha256
from mmgen.common import *
crmsg = {
@ -49,7 +50,7 @@ def scramble_seed(seed,scramble_key,hash_rounds):
return sha256_rounds(scr_seed,hash_rounds)
def encrypt_seed(seed,key):
return encrypt_data(seed,key,iv=1,desc='seed')
return encrypt_data(seed,key,desc='seed')
def decrypt_seed(enc_seed,key,seed_id,key_id):
vmsg_r('Checking key...')
@ -59,7 +60,7 @@ def decrypt_seed(enc_seed,key,seed_id,key_id):
msg('Incorrect passphrase or hash preset')
return False
dec_seed = decrypt_data(enc_seed,key,iv=1,desc='seed')
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'):
@ -79,31 +80,28 @@ def decrypt_seed(enc_seed,key,seed_id,key_id):
dmsg('Decrypted seed: {}'.format(dec_seed.hex()))
return dec_seed
def encrypt_data(data,key,iv=1,desc='data',verify=True):
# 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR
from Crypto.Cipher import AES
from Crypto.Util import Counter
def encrypt_data(data,key,iv=g.aesctr_dfl_iv,desc='data',verify=True):
vmsg('Encrypting {}'.format(desc))
c = AES.new(key,AES.MODE_CTR,counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
enc_data = c.encrypt(data)
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('Performing a test decryption of the {}...'.format(desc))
c = AES.new(key,AES.MODE_CTR,counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
dec_data = c.decrypt(enc_data)
if dec_data == data: vmsg('done')
else:
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,"ERROR.\nDecrypted {s} doesn't match original {s}".format(s=desc))
vmsg('done')
return enc_data
def decrypt_data(enc_data,key,iv=1,desc='data'):
from Crypto.Cipher import AES
from Crypto.Util import Counter
def decrypt_data(enc_data,key,iv=g.aesctr_dfl_iv,desc='data'):
vmsg_r('Decrypting {} with key...'.format(desc))
c = AES.new(key,AES.MODE_CTR,counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
return c.decrypt(enc_data)
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):
@ -153,8 +151,7 @@ def _get_random_data_from_user(uchars):
return key_data+''.join(fmt_time_data).encode()
def get_random(length):
from Crypto import Random
os_rand = Random.new().read(length)
os_rand = os.urandom(length)
if opt.usr_randchars:
from_what = 'OS random data'
if not g.user_entropy:
@ -163,7 +160,7 @@ def get_random(length):
from_what += ' plus user-supplied entropy'
else:
from_what += ' plus saved user-supplied entropy'
key = make_key(g.user_entropy,'','2',from_what=from_what,verbose=True)
key = make_key(g.user_entropy,b'','2',from_what=from_what,verbose=True)
return encrypt_data(os_rand,key,desc='random data',verify=False)
else:
return os_rand
@ -195,7 +192,7 @@ def mmgen_encrypt(data,desc='data',hash_preset=''):
qmsg("Using {} hash preset of '{}'".format(m,hp))
passwd = get_new_passphrase(desc,{})
key = make_key(passwd,salt,hp)
enc_d = encrypt_data(sha256(nonce+data).digest()+nonce+data,key,int(iv.hex(),16),desc=desc)
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=''):
@ -210,7 +207,7 @@ def mmgen_decrypt(data,desc='data',hash_preset=''):
qmsg("Using {} hash preset of '{}'".format(m,hp))
passwd = get_mmgen_passphrase(desc)
key = make_key(passwd,salt,hp)
dec_d = decrypt_data(enc_d,key,int(iv.hex(),16),desc)
dec_d = decrypt_data(enc_d,key,iv,desc)
if dec_d[:_sha256_len] == sha256(dec_d[_sha256_len:]).digest():
vmsg('OK')
return dec_d[_sha256_len+_nonce_len:]

View file

@ -191,6 +191,7 @@ class g(object):
mmenc_ext = 'mmenc'
salt_len = 16
aesctr_iv_len = 16
aesctr_dfl_iv = b'\x00' * (aesctr_iv_len-1) + b'\x01'
hincog_chk_len = 8
key_generators = 'python-ecdsa','secp256k1' # '1','2'

View file

@ -855,7 +855,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
d.salt = get_random(g.salt_len)
key = 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, 1, 'seed')
d.enc_seed = encrypt_data(chk+self.seed.data, key, g.aesctr_dfl_iv, 'seed')
d.wrapper_key = make_key(d.passwd, d.iv, d.hash_preset, 'incog wrapper key')
d.key_id = make_chksum_8(d.wrapper_key)
@ -864,13 +864,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
def _format(self):
d = self.ssdata
# print len(d.iv), len(d.salt), len(d.enc_seed), len(d.wrapper_key)
self.fmt_data = d.iv + encrypt_data(
d.salt + d.enc_seed,
d.wrapper_key,
int(d.iv.hex(),16),
self.desc)
# print len(self.fmt_data)
self.fmt_data = d.iv + encrypt_data(d.salt+d.enc_seed, d.wrapper_key, d.iv, self.desc)
def _filename(self):
s = self.seed
@ -921,8 +915,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
# 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,
int(d.iv.hex(),16), 'incog data')
dd = 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:]