Skip to content

Commit 3aa55f4

Browse files
authored
Merge pull request #386 from t184256/aes-fixes
AES fixes
2 parents 3d3e83a + db757e2 commit 3aa55f4

File tree

3 files changed

+113
-24
lines changed

3 files changed

+113
-24
lines changed

tlslite/utils/openssl_aes.py

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,52 @@
99
if m2cryptoLoaded:
1010

1111
def new(key, mode, IV):
12+
# IV argument name is a part of the interface
13+
# pylint: disable=invalid-name
1214
return OpenSSL_AES(key, mode, IV)
1315

1416
class OpenSSL_AES(AES):
1517

1618
def __init__(self, key, mode, IV):
19+
# IV argument/field names are a part of the interface
20+
# pylint: disable=invalid-name
1721
AES.__init__(self, key, mode, IV, "openssl")
18-
self.key = key
19-
self.IV = IV
22+
self.IV, self._key = IV, key
23+
self._context = None
24+
self._encrypt = None
2025

21-
def _createContext(self, encrypt):
22-
context = m2.cipher_ctx_new()
23-
if len(self.key)==16:
26+
def _init_context(self, encrypt=True):
27+
if len(self._key) == 16:
2428
cipherType = m2.aes_128_cbc()
25-
if len(self.key)==24:
29+
if len(self._key) == 24:
2630
cipherType = m2.aes_192_cbc()
27-
if len(self.key)==32:
31+
if len(self._key) == 32:
2832
cipherType = m2.aes_256_cbc()
29-
m2.cipher_init(context, cipherType, self.key, self.IV, encrypt)
30-
return context
33+
self._context = m2.cipher_ctx_new()
34+
m2.cipher_init(self._context, cipherType, self._key, self.IV,
35+
int(encrypt))
36+
m2.cipher_set_padding(self._context, 0)
37+
self._encrypt = encrypt
3138

3239
def encrypt(self, plaintext):
40+
if self._context is None:
41+
self._init_context(encrypt=True)
42+
else:
43+
assert self._encrypt, '.encrypt() not allowed after .decrypt()'
3344
AES.encrypt(self, plaintext)
34-
context = self._createContext(1)
35-
ciphertext = m2.cipher_update(context, plaintext)
36-
m2.cipher_ctx_free(context)
37-
self.IV = ciphertext[-self.block_size:]
45+
ciphertext = m2.cipher_update(self._context, plaintext)
3846
return bytearray(ciphertext)
3947

4048
def decrypt(self, ciphertext):
49+
if self._context is None:
50+
self._init_context(encrypt=False)
51+
else:
52+
assert not self._encrypt, \
53+
'.decrypt() not allowed after .encrypt()'
4154
AES.decrypt(self, ciphertext)
42-
context = self._createContext(0)
43-
#I think M2Crypto has a bug - it fails to decrypt and return the last block passed in.
44-
#To work around this, we append sixteen zeros to the string, below:
45-
plaintext = m2.cipher_update(context, ciphertext+(b'\0'*16))
46-
47-
#If this bug is ever fixed, then plaintext will end up having a garbage
48-
#plaintext block on the end. That's okay - the below code will discard it.
49-
plaintext = plaintext[:len(ciphertext)]
50-
m2.cipher_ctx_free(context)
51-
self.IV = ciphertext[-self.block_size:]
55+
plaintext = m2.cipher_update(self._context, ciphertext)
5256
return bytearray(plaintext)
57+
58+
def __del__(self):
59+
if self._context is not None:
60+
m2.cipher_ctx_free(self._context)

tlslite/utils/python_aes.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,24 @@
1111

1212

1313
def new(key, mode, IV):
14+
# IV argument name is a part of the interface
15+
# pylint: disable=invalid-name
1416
return Python_AES(key, mode, IV)
1517

1618

1719
class Python_AES(AES):
1820
def __init__(self, key, mode, IV):
21+
# IV argument/field names are a part of the interface
22+
# pylint: disable=invalid-name
23+
key, IV = bytearray(key), bytearray(IV)
1924
super(Python_AES, self).__init__(key, mode, IV, "python")
2025
self.rijndael = Rijndael(key, 16)
2126
self.IV = IV
2227

2328
def encrypt(self, plaintext):
2429
super(Python_AES, self).encrypt(plaintext)
2530

26-
plaintextBytes = plaintext[:]
31+
plaintextBytes = bytearray(plaintext)
2732
chainBytes = self.IV[:]
2833

2934
#CBC Mode: For each block...
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Copyright (c) 2019, Alexander Sosedkin
2+
#
3+
# See the LICENSE file for legal information regarding use of this file.
4+
5+
# compatibility with Python 2.6, for that we need unittest2 package,
6+
# which is not available on 3.3 or 3.4
7+
try:
8+
import unittest2 as unittest
9+
except ImportError:
10+
import unittest
11+
12+
import sys
13+
14+
from hypothesis import given, assume, settings
15+
from hypothesis.strategies import binary, integers, tuples
16+
17+
from tlslite.utils import cryptomath
18+
19+
import tlslite.utils.python_aes
20+
py_aes = lambda key, iv: tlslite.utils.python_aes.new(key, 2, iv)
21+
22+
23+
HYP_SETTINGS = {'deadline': None} if sys.version_info > (2, 7) else {}
24+
25+
26+
class TestAES(unittest.TestCase):
27+
_given = given(binary(min_size=24, max_size=24), # key
28+
binary(min_size=16, max_size=16), # iv
29+
binary(min_size=13*16, max_size=13*16), # plaintext
30+
(tuples(integers(0, 13), integers(0, 13)) # split points
31+
.filter(lambda split_pts: split_pts[0] <= split_pts[1])
32+
.map(lambda lengths: [i * 16 for i in lengths])))
33+
34+
def split_test(self, key, iv, plaintext, split_points, make_impl=py_aes):
35+
i, j = split_points
36+
37+
ciphertext = make_impl(key, iv).encrypt(plaintext)
38+
self.assertEqual(make_impl(key, iv).decrypt(ciphertext), plaintext)
39+
40+
impl = make_impl(key, iv)
41+
pl1, pl2, pl3 = plaintext[:i], plaintext[i:j], plaintext[j:]
42+
ci1, ci2, ci3 = impl.encrypt(pl1), impl.encrypt(pl2), impl.encrypt(pl3)
43+
self.assertEqual(ci1 + ci2 + ci3, ciphertext)
44+
45+
impl = make_impl(key, iv)
46+
pl1, pl2, pl3 = impl.decrypt(ci1), impl.decrypt(ci2), impl.decrypt(ci3)
47+
self.assertEqual(pl1 + pl2 + pl3, plaintext)
48+
49+
return ciphertext
50+
51+
@_given
52+
@settings(**HYP_SETTINGS)
53+
def test_python(self, key, iv, plaintext, split_points):
54+
self.split_test(key, iv, plaintext, split_points)
55+
56+
@unittest.skipIf(not cryptomath.m2cryptoLoaded, "requires M2Crypto")
57+
@_given
58+
@settings(**HYP_SETTINGS)
59+
def test_python_vs_mcrypto(self, key, iv, plaintext, split_points):
60+
import tlslite.utils.openssl_aes
61+
m2_aes = lambda k, iv: tlslite.utils.openssl_aes.new(k, 2, iv)
62+
63+
py_res = self.split_test(key, iv, plaintext, split_points, py_aes)
64+
m2_res = self.split_test(key, iv, plaintext, split_points, m2_aes)
65+
self.assertEqual(py_res, m2_res)
66+
67+
@unittest.skipIf(not cryptomath.pycryptoLoaded, "requires pycrypto")
68+
@_given
69+
@settings(**HYP_SETTINGS)
70+
def test_python_vs_pycrypto(self, key, iv, plaintext, split_points):
71+
import tlslite.utils.pycrypto_aes
72+
pc_aes = lambda k, iv: tlslite.utils.pycrypto_aes.new(k, 2, iv)
73+
74+
py_res = self.split_test(key, iv, plaintext, split_points, py_aes)
75+
pc_res = self.split_test(key, iv, plaintext, split_points, pc_aes)
76+
self.assertEqual(py_res, pc_res)

0 commit comments

Comments
 (0)