Browse Source

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
MMGen 6 years ago
parent
commit
8a3b9216f6
3 changed files with 25 additions and 34 deletions
  1. 21 24
      mmgen/crypto.py
  2. 1 0
      mmgen/globalvars.py
  3. 3 10
      mmgen/seed.py

+ 21 - 24
mmgen/crypto.py

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

+ 1 - 0
mmgen/globalvars.py

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

+ 3 - 10
mmgen/seed.py

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