From 11a9b1fc787a8384f02f6e9a95a8195e74c163b5 Mon Sep 17 00:00:00 2001 From: Ivan Nikolchev Date: Wed, 18 Dec 2019 16:22:55 +0100 Subject: [PATCH 1/2] Add NIST test vectors for AES-CBC --- unit_tests/test_tlslite_utils_aescbc.py | 215 ++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 unit_tests/test_tlslite_utils_aescbc.py diff --git a/unit_tests/test_tlslite_utils_aescbc.py b/unit_tests/test_tlslite_utils_aescbc.py new file mode 100644 index 000000000..f5b4da1fa --- /dev/null +++ b/unit_tests/test_tlslite_utils_aescbc.py @@ -0,0 +1,215 @@ +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.utils.rijndael import Rijndael +from tlslite.utils.python_aes import Python_AES + + +class TestAESCBC(unittest.TestCase): + def test___init__(self): + key = bytearray(16) + aesCBC = Python_AES(key, 2, bytearray(b'\x00' * 16)) + + self.assertIsNotNone(aesCBC) + + def test___init___with_invalid_key(self): + key = bytearray(8) + + with self.assertRaises(AssertionError): + aesCBC = Python_AES(key, 2, bytearray(b'\x00' * 16)) + + def test___init___with_invalid_iv(self): + key = bytearray(16) + + with self.assertRaises(AssertionError): + aesCBC = Python_AES(key, 2, bytearray(b'\x00' * 8)) + + def test_encrypt_with_test_vector_1(self): + + key = bytearray(b'\x2b\x7e\x15\x16\x28\xae\xd2' + b'\xa6\xab\xf7\x15\x88\x09\xcf\x4f\x3c') + + IV = bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + ciphertext = bytearray(b'\x76\x49\xab\xac\x81\x19\xb2\x46' + b'\xce\xe9\x8e\x9b\x12\xe9\x19\x7d' + b'\x50\x86\xcb\x9b\x50\x72\x19\xee' + b'\x95\xdb\x11\x3a\x91\x76\x78\xb2' + b'\x73\xbe\xd6\xb8\xe3\xc1\x74\x3b' + b'\x71\x16\xe6\x9e\x22\x22\x95\x16' + b'\x3f\xf1\xca\xa1\x68\x1f\xac\x09' + b'\x12\x0e\xca\x30\x75\x86\xe1\xa7') + + aesCBC = Python_AES(key, 2, IV) + self.assertEqual(aesCBC.encrypt(plaintext), ciphertext) + + def test_decrypt_with_test_vector_1(self): + + key = bytearray(b'\x2b\x7e\x15\x16\x28\xae\xd2' + b'\xa6\xab\xf7\x15\x88\x09\xcf\x4f\x3c') + + IV = bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + ciphertext = bytearray(b'\x76\x49\xab\xac\x81\x19\xb2\x46' + b'\xce\xe9\x8e\x9b\x12\xe9\x19\x7d' + b'\x50\x86\xcb\x9b\x50\x72\x19\xee' + b'\x95\xdb\x11\x3a\x91\x76\x78\xb2' + b'\x73\xbe\xd6\xb8\xe3\xc1\x74\x3b' + b'\x71\x16\xe6\x9e\x22\x22\x95\x16' + b'\x3f\xf1\xca\xa1\x68\x1f\xac\x09' + b'\x12\x0e\xca\x30\x75\x86\xe1\xa7') + + aesCBC = Python_AES(key, 2, IV) + self.assertEqual(aesCBC.decrypt(ciphertext), plaintext) + + def test_encrypt_with_test_vector_2(self): + + key = bytearray(b'\x8e\x73\xb0\xf7\xda\x0e\x64\x52' + b'\xc8\x10\xf3\x2b\x80\x90\x79\xe5' + b'\x62\xf8\xea\xd2\x52\x2c\x6b\x7b') + + IV = bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + ciphertext = bytearray(b'\x4f\x02\x1d\xb2\x43\xbc\x63\x3d' + b'\x71\x78\x18\x3a\x9f\xa0\x71\xe8' + b'\xb4\xd9\xad\xa9\xad\x7d\xed\xf4' + b'\xe5\xe7\x38\x76\x3f\x69\x14\x5a' + b'\x57\x1b\x24\x20\x12\xfb\x7a\xe0' + b'\x7f\xa9\xba\xac\x3d\xf1\x02\xe0' + b'\x08\xb0\xe2\x79\x88\x59\x88\x81' + b'\xd9\x20\xa9\xe6\x4f\x56\x15\xcd') + + aesCBC = Python_AES(key, 2, IV) + self.assertEqual(aesCBC.encrypt(plaintext), ciphertext) + + def test_decrypt_with_test_vector_2(self): + + key = bytearray(b'\x8e\x73\xb0\xf7\xda\x0e\x64\x52' + b'\xc8\x10\xf3\x2b\x80\x90\x79\xe5' + b'\x62\xf8\xea\xd2\x52\x2c\x6b\x7b') + + IV = bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + ciphertext = bytearray(b'\x4f\x02\x1d\xb2\x43\xbc\x63\x3d' + b'\x71\x78\x18\x3a\x9f\xa0\x71\xe8' + b'\xb4\xd9\xad\xa9\xad\x7d\xed\xf4' + b'\xe5\xe7\x38\x76\x3f\x69\x14\x5a' + b'\x57\x1b\x24\x20\x12\xfb\x7a\xe0' + b'\x7f\xa9\xba\xac\x3d\xf1\x02\xe0' + b'\x08\xb0\xe2\x79\x88\x59\x88\x81' + b'\xd9\x20\xa9\xe6\x4f\x56\x15\xcd') + + aesCBC = Python_AES(key, 2, IV) + self.assertEqual(aesCBC.decrypt(ciphertext), plaintext) + + def test_encrypt_with_test_vector_3(self): + + key = bytearray(b'\x60\x3d\xeb\x10\x15\xca\x71\xbe' + b'\x2b\x73\xae\xf0\x85\x7d\x77\x81' + b'\x1f\x35\x2c\x07\x3b\x61\x08\xd7' + b'\x2d\x98\x10\xa3\x09\x14\xdf\xf4') + + IV = bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + ciphertext = bytearray(b'\xf5\x8c\x4c\x04\xd6\xe5\xf1\xba' + b'\x77\x9e\xab\xfb\x5f\x7b\xfb\xd6' + b'\x9c\xfc\x4e\x96\x7e\xdb\x80\x8d' + b'\x67\x9f\x77\x7b\xc6\x70\x2c\x7d' + b'\x39\xf2\x33\x69\xa9\xd9\xba\xcf' + b'\xa5\x30\xe2\x63\x04\x23\x14\x61' + b'\xb2\xeb\x05\xe2\xc3\x9b\xe9\xfc' + b'\xda\x6c\x19\x07\x8c\x6a\x9d\x1b') + + aesCBC = Python_AES(key, 2, IV) + self.assertEqual(aesCBC.encrypt(plaintext), ciphertext) + + def test_decrypt_with_test_vector_3(self): + + key = bytearray(b'\x60\x3d\xeb\x10\x15\xca\x71\xbe' + b'\x2b\x73\xae\xf0\x85\x7d\x77\x81' + b'\x1f\x35\x2c\x07\x3b\x61\x08\xd7' + b'\x2d\x98\x10\xa3\x09\x14\xdf\xf4') + + IV = bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + ciphertext = bytearray(b'\xf5\x8c\x4c\x04\xd6\xe5\xf1\xba' + b'\x77\x9e\xab\xfb\x5f\x7b\xfb\xd6' + b'\x9c\xfc\x4e\x96\x7e\xdb\x80\x8d' + b'\x67\x9f\x77\x7b\xc6\x70\x2c\x7d' + b'\x39\xf2\x33\x69\xa9\xd9\xba\xcf' + b'\xa5\x30\xe2\x63\x04\x23\x14\x61' + b'\xb2\xeb\x05\xe2\xc3\x9b\xe9\xfc' + b'\xda\x6c\x19\x07\x8c\x6a\x9d\x1b') + + aesCBC = Python_AES(key, 2, IV) + self.assertEqual(aesCBC.decrypt(ciphertext), plaintext) From 2c0d12cfc16bbb517cdebcdc631e6917e958dda1 Mon Sep 17 00:00:00 2001 From: Ivan Nikolchev Date: Wed, 18 Dec 2019 16:32:54 +0100 Subject: [PATCH 2/2] Separate AES-CTR code and replace it in AES-CCM and AES-GCM --- tlslite/utils/aes.py | 10 +- tlslite/utils/aesccm.py | 65 ++----- tlslite/utils/aesgcm.py | 21 +-- tlslite/utils/cipherfactory.py | 22 ++- tlslite/utils/python_aes.py | 39 ++++- unit_tests/test_tlslite_utils_aesctr.py | 217 ++++++++++++++++++++++++ 6 files changed, 306 insertions(+), 68 deletions(-) create mode 100644 unit_tests/test_tlslite_utils_aesctr.py diff --git a/tlslite/utils/aes.py b/tlslite/utils/aes.py index 5a038fb9f..02ab11abe 100644 --- a/tlslite/utils/aes.py +++ b/tlslite/utils/aes.py @@ -7,10 +7,14 @@ class AES(object): def __init__(self, key, mode, IV, implementation): if len(key) not in (16, 24, 32): raise AssertionError() - if mode != 2: - raise AssertionError() - if len(IV) != 16: + if mode not in [2, 6]: raise AssertionError() + if mode == 2: + if len(IV) != 16: + raise AssertionError() + if mode == 6: + if len(IV) not in [8, 12]: + raise AssertionError() self.isBlockCipher = True self.isAEAD = False self.block_size = 16 diff --git a/tlslite/utils/aesccm.py b/tlslite/utils/aesccm.py index 23b7c6aed..7639b91c5 100644 --- a/tlslite/utils/aesccm.py +++ b/tlslite/utils/aesccm.py @@ -4,10 +4,8 @@ # from __future__ import division -from tlslite.utils.cryptomath import numberToByteArray, divceil -from tlslite.utils.python_aes import Python_AES -import sys -import array +from tlslite.utils.cryptomath import numberToByteArray +from tlslite.utils import python_aes class AESCCM(object): @@ -30,7 +28,8 @@ def __init__(self, key, implementation, rawAesEncrypt, tag_length=16): self._rawAesEncrypt = rawAesEncrypt self.implementation = implementation self.nonceLength = 12 - self._cbc = Python_AES(self.key, 2, bytearray(b'\x00' * 16)) + self._cbc = python_aes.new(self.key, 2, bytearray(b'\x00' * 16)) + self._ctr = python_aes.new(self.key, 6, bytearray(b'\x00' * 12)) def _cbcmac_calc(self, nonce, aad, msg): L = 15 - len(nonce) @@ -89,7 +88,6 @@ def seal(self, nonce, msg, aad): raise ValueError("Bad nonce length") L = 15 - len(nonce) - auth_value = bytearray(self.tagLength) # We construct the key stream blocks. # S_0 is not used for encrypting the message, it is only used @@ -97,17 +95,16 @@ def seal(self, nonce, msg, aad): # S_1..S_n are used to encrypt the message. flags = L - 1 - s_0 = self._rawAesEncrypt(bytearray([flags]) + - nonce + numberToByteArray(0, L)) + s_0 = bytearray([flags]) + nonce + numberToByteArray(0, L) + s_n = bytearray([flags]) + nonce + numberToByteArray(1, L) - s_n = self._construct_s_n(msg, flags, nonce, L) - - enc_msg = self._xor(msg, s_n) + self._ctr.counter = s_n + enc_msg = self._ctr.encrypt(msg) mac = self._cbcmac_calc(nonce, aad, msg) - for i in range(self.tagLength): - auth_value[i] = mac[i] ^ s_0[i] + self._ctr.counter = s_0 + auth_value = self._ctr.encrypt(mac) ciphertext = enc_msg + auth_value return ciphertext @@ -122,61 +119,31 @@ def open(self, nonce, ciphertext, aad): return None L = 15 - len(nonce) - received_mac = bytearray(self.tagLength) flags = L - 1 # Same construction as in seal function - s_0 = self._rawAesEncrypt(bytearray([flags]) + - nonce + numberToByteArray(0, L)) - - s_n = self._construct_s_n(ciphertext, flags, nonce, L) + s_0 = bytearray([flags]) + nonce + numberToByteArray(0, L) + s_n = bytearray([flags]) + nonce + numberToByteArray(1, L) - msg = self._xor(ciphertext, s_n) + self._ctr.counter = s_n + msg = self._ctr.decrypt(ciphertext) msg = msg[:-self.tagLength] auth_value = ciphertext[-self.tagLength:] computed_mac = self._cbcmac_calc(nonce, aad, msg) # We decrypt the auth value - for i in range(self.tagLength): - received_mac[i] = auth_value[i] ^ s_0[i] + self._ctr.counter = s_0 + received_mac = self._ctr.decrypt(auth_value) # Compare the mac vlaue is the same as the one we computed if received_mac != computed_mac: return None return msg - def _construct_s_n(self, ciphertext, flags, nonce, L): - s_n = bytearray() - counter_lmt = divceil(len(ciphertext), 16) - for i in range(1, int(counter_lmt) + 1): - s_n += self._rawAesEncrypt(bytearray([flags]) + - nonce + numberToByteArray(i, L)) - return s_n - - if sys.version_info[0] >= 3: - def _xor(self, inp, s_n): - inp_added = -((8 - (len(inp) % 8)) % 8) or None - self._pad_with_zeroes(inp, 8) - msg = self._use_memoryview(inp, s_n)[:inp_added] - inp[:] = inp[:inp_added] - return msg - else: - def _xor(self, inp, s_n): - msg = bytearray(i ^ j for i, j in zip(inp, s_n)) - return msg - @staticmethod def _pad_with_zeroes(data, size): if len(data) % size != 0: zeroes_to_add = size - (len(data) % size) data += b'\x00' * zeroes_to_add - - @staticmethod - def _use_memoryview(msg, s_n): - msg_mv = memoryview(msg).cast('Q') - s_n_mv = memoryview(s_n).cast('Q') - enc_arr = array.array('Q', (i ^ j for i, j in zip(msg_mv, s_n_mv))) - enc_msg = bytearray(enc_arr.tobytes()) - return enc_msg diff --git a/tlslite/utils/aesgcm.py b/tlslite/utils/aesgcm.py index 1dccb4ca2..41aab9f41 100644 --- a/tlslite/utils/aesgcm.py +++ b/tlslite/utils/aesgcm.py @@ -14,6 +14,7 @@ # look-up table. from __future__ import division +from tlslite.utils import python_aes from .constanttime import ct_compare_digest from .cryptomath import bytesToNumber, numberToByteArray @@ -35,6 +36,8 @@ def __init__(self, key, implementation, rawAesEncrypt): self.name = "aes256gcm" else: raise AssertionError() + self.key = key + self._ctr = python_aes.new(self.key, 6, bytearray(b'\x00' * 12)) self._rawAesEncrypt = rawAesEncrypt @@ -53,18 +56,6 @@ def __init__(self, key, implementation, rawAesEncrypt): self._productTable[self._reverseBits(i+1)] = \ self._gcmAdd(self._productTable[self._reverseBits(i)], h) - def _rawAesCtrEncrypt(self, counter, inp): - """ - Encrypts (or decrypts) plaintext with AES-CTR. counter is modified. - """ - out = bytearray(len(inp)) - rawAesEncrypt = self._rawAesEncrypt - for i in range(0, len(out), 16): - mask = rawAesEncrypt(counter) - for j in range(i, min(len(out), i + 16)): - out[j] = inp[j] ^ mask[j-i] - self._inc32(counter) - return out def _auth(self, ciphertext, ad, tagMask): y = 0 @@ -125,7 +116,8 @@ def seal(self, nonce, plaintext, data): # The counter starts at 2 for the actual encryption. counter[-1] = 2 - ciphertext = self._rawAesCtrEncrypt(counter, plaintext) + self._ctr.counter = counter + ciphertext = self._ctr.encrypt(plaintext) tag = self._auth(ciphertext, data, tagMask) @@ -158,7 +150,8 @@ def open(self, nonce, ciphertext, data): # The counter starts at 2 for the actual decryption. counter[-1] = 2 - return self._rawAesCtrEncrypt(counter, ciphertext) + self._ctr.counter = counter + return self._ctr.decrypt(ciphertext) @staticmethod def _reverseBits(i): diff --git a/tlslite/utils/cipherfactory.py b/tlslite/utils/cipherfactory.py index ef418ace4..f290bef49 100644 --- a/tlslite/utils/cipherfactory.py +++ b/tlslite/utils/cipherfactory.py @@ -53,7 +53,27 @@ def createAES(key, IV, implList=None): elif impl == "pycrypto" and cryptomath.pycryptoLoaded: return pycrypto_aes.new(key, 2, IV) elif impl == "python": - return python_aes.new(key, 2, IV) + return python_aes.new(key, 2, IV) + raise NotImplementedError() + +def createAESCTR(key, IV, implList=None): + """Create a new AESCTR object. + + :type key: str + :param key: A 16, 24, or 32 byte string. + + :type IV: str + :param IV: A 8 or 12 byte string + + :rtype: tlslite.utils.AES + :returns: An AES object. + """ + if implList is None: + implList = ["python"] + + for impl in implList: + if impl == "python": + return python_aes.new(key, 6, IV) raise NotImplementedError() def createAESGCM(key, implList=None): diff --git a/tlslite/utils/python_aes.py b/tlslite/utils/python_aes.py index e792823f1..c1b20adb5 100644 --- a/tlslite/utils/python_aes.py +++ b/tlslite/utils/python_aes.py @@ -13,7 +13,10 @@ def new(key, mode, IV): # IV argument name is a part of the interface # pylint: disable=invalid-name - return Python_AES(key, mode, IV) + if mode == 2: + return Python_AES(key, mode, IV) + elif mode == 6: + return Python_AES_CTR(key, mode, IV) class Python_AES(AES): @@ -75,3 +78,37 @@ def decrypt(self, ciphertext): self.IV = chainBytes[:] return ciphertextBytes + + +class Python_AES_CTR(AES): + def __init__(self, key, mode, IV): + super(Python_AES_CTR, self).__init__(key, mode, IV, "python") + self.rijndael = Rijndael(key, 16) + self.IV = IV + if len(self.IV) not in [8, 12]: + raise ValueError("IV needs to be 8 or 12 bytes") + self.counter_bytes = 16 - len(self.IV) + self.counter = self.IV + bytearray(b'\x00' * self.counter_bytes) + + def _counter(self, counter): + for i in range(len(counter)-1, + len(counter)-(self.counter_bytes + 1), -1): + counter[i] = (counter[i] + 1) % 256 + if counter[i] != 0: + if counter[-self.counter_bytes:] == \ + bytearray(b'\x00' * self.counter_bytes): + raise OverflowError("CTR counter overflowed") + break + return counter + + def encrypt(self, plaintext): + + mask = bytearray() + while len(mask) < len(plaintext): + mask += self.rijndael.encrypt(self.counter) + self._counter(self.counter) + inp_bytes = bytearray(i ^ j for i, j in zip(plaintext, mask)) + return inp_bytes + + def decrypt(self, ciphertext): + return self.encrypt(ciphertext) diff --git a/unit_tests/test_tlslite_utils_aesctr.py b/unit_tests/test_tlslite_utils_aesctr.py new file mode 100644 index 000000000..2afa27d0f --- /dev/null +++ b/unit_tests/test_tlslite_utils_aesctr.py @@ -0,0 +1,217 @@ +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.utils.rijndael import Rijndael +from tlslite.utils.python_aes import Python_AES_CTR + + +class TestAESCTR(unittest.TestCase): + def test___init__(self): + key = bytearray(16) + aesCTR = Python_AES_CTR(key, mode=6, IV=bytearray(b'\x00' * 12)) + + self.assertIsNotNone(aesCTR) + + def test___init___with_invalid_key(self): + key = bytearray(8) + + with self.assertRaises(AssertionError): + aesCTR = Python_AES_CTR(key, mode=6, IV=bytearray(b'\x00' * 12)) + + def test_encrypt_with_test_vector_1(self): + + key = bytearray(b'\x2b\x7e\x15\x16\x28\xae\xd2' + b'\xa6\xab\xf7\x15\x88\x09\xcf\x4f\x3c') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + ciphertext = bytearray(b'\x87\x4d\x61\x91\xb6\x20\xe3' + b'\x26\x1b\xef\x68\x64\x99\x0d' + b'\xb6\xce\x98\x06\xf6\x6b\x79' + b'\x70\xfd\xff\x86\x17\x18\x7b' + b'\xb9\xff\xfd\xff\x5a\xe4\xdf' + b'\x3e\xdb\xd5\xd3\x5e\x5b\x4f' + b'\x09\x02\x0d\xb0\x3e\xab\x1e' + b'\x03\x1d\xda\x2f\xbe\x03\xd1' + b'\x79\x21\x70\xa0\xf3\x00\x9c\xee') + + counter = bytearray(b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8' + b'\xf9\xfa\xfb\xfc\xfd\xfe\xff') + + aesCTR = Python_AES_CTR(key, mode=6, IV=bytearray(b'\x00' * 12)) + aesCTR.counter = counter + self.assertEqual(aesCTR.encrypt(plaintext), ciphertext) + + def test_decrypt_with_test_vector_1(self): + + key = bytearray(b'\x2b\x7e\x15\x16\x28\xae\xd2' + b'\xa6\xab\xf7\x15\x88\x09\xcf\x4f\x3c') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + ciphertext = bytearray(b'\x87\x4d\x61\x91\xb6\x20\xe3' + b'\x26\x1b\xef\x68\x64\x99\x0d' + b'\xb6\xce\x98\x06\xf6\x6b\x79' + b'\x70\xfd\xff\x86\x17\x18\x7b' + b'\xb9\xff\xfd\xff\x5a\xe4\xdf' + b'\x3e\xdb\xd5\xd3\x5e\x5b\x4f' + b'\x09\x02\x0d\xb0\x3e\xab\x1e' + b'\x03\x1d\xda\x2f\xbe\x03\xd1' + b'\x79\x21\x70\xa0\xf3\x00\x9c\xee') + + counter = bytearray(b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8' + b'\xf9\xfa\xfb\xfc\xfd\xfe\xff') + + aesCTR = Python_AES_CTR(key, mode=6, IV=bytearray(b'\x00' * 12)) + aesCTR.counter = counter + self.assertEqual(aesCTR.decrypt(ciphertext), plaintext) + + def test_encrypt_with_test_vector_2(self): + + key = bytearray(b'\x8e\x73\xb0\xf7\xda\x0e\x64\x52\xc8' + b'\x10\xf3\x2b\x80\x90\x79\xe5\x62\xf8' + b'\xea\xd2\x52\x2c\x6b\x7b') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + ciphertext = bytearray(b'\x1a\xbc\x93\x24\x17\x52\x1c\xa2' + b'\x4f\x2b\x04\x59\xfe\x7e\x6e\x0b' + b'\x09\x03\x39\xec\x0a\xa6\xfa\xef' + b'\xd5\xcc\xc2\xc6\xf4\xce\x8e\x94' + b'\x1e\x36\xb2\x6b\xd1\xeb\xc6\x70' + b'\xd1\xbd\x1d\x66\x56\x20\xab\xf7' + b'\x4f\x78\xa7\xf6\xd2\x98\x09\x58' + b'\x5a\x97\xda\xec\x58\xc6\xb0\x50') + + counter = bytearray(b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8' + b'\xf9\xfa\xfb\xfc\xfd\xfe\xff') + + aesCTR = Python_AES_CTR(key, mode=6, IV=bytearray(b'\x00' * 12)) + aesCTR.counter = counter + self.assertEqual(aesCTR.encrypt(plaintext), ciphertext) + + def test_decrypt_with_test_vector_2(self): + + key = bytearray(b'\x8e\x73\xb0\xf7\xda\x0e\x64\x52\xc8' + b'\x10\xf3\x2b\x80\x90\x79\xe5\x62\xf8' + b'\xea\xd2\x52\x2c\x6b\x7b') + + ciphertext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + plaintext = bytearray(b'\x1a\xbc\x93\x24\x17\x52\x1c\xa2' + b'\x4f\x2b\x04\x59\xfe\x7e\x6e\x0b' + b'\x09\x03\x39\xec\x0a\xa6\xfa\xef' + b'\xd5\xcc\xc2\xc6\xf4\xce\x8e\x94' + b'\x1e\x36\xb2\x6b\xd1\xeb\xc6\x70' + b'\xd1\xbd\x1d\x66\x56\x20\xab\xf7' + b'\x4f\x78\xa7\xf6\xd2\x98\x09\x58' + b'\x5a\x97\xda\xec\x58\xc6\xb0\x50') + + counter = bytearray(b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8' + b'\xf9\xfa\xfb\xfc\xfd\xfe\xff') + + aesCTR = Python_AES_CTR(key, mode=6, IV=bytearray(b'\x00' * 12)) + aesCTR.counter = counter + self.assertEqual(aesCTR.decrypt(ciphertext), plaintext) + + def test_encrypt_with_test_vector_3(self): + + key = bytearray(b'\x60\x3d\xeb\x10\x15\xca\x71\xbe' + b'\x2b\x73\xae\xf0\x85\x7d\x77\x81' + b'\x1f\x35\x2c\x07\x3b\x61\x08\xd7' + b'\x2d\x98\x10\xa3\x09\x14\xdf\xf4') + + ciphertext = bytearray(b'\x60\x1e\xc3\x13\x77\x57\x89\xa5' + b'\xb7\xa7\xf5\x04\xbb\xf3\xd2\x28' + b'\xf4\x43\xe3\xca\x4d\x62\xb5\x9a' + b'\xca\x84\xe9\x90\xca\xca\xf5\xc5' + b'\x2b\x09\x30\xda\xa2\x3d\xe9\x4c' + b'\xe8\x70\x17\xba\x2d\x84\x98\x8d' + b'\xdf\xc9\xc5\x8d\xb6\x7a\xad\xa6' + b'\x13\xc2\xdd\x08\x45\x79\x41\xa6') + + plaintext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + counter = bytearray(b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8' + b'\xf9\xfa\xfb\xfc\xfd\xfe\xff') + + aesCTR = Python_AES_CTR(key, mode=6, IV=bytearray(b'\x00' * 12)) + aesCTR.counter = counter + self.assertEqual(aesCTR.encrypt(plaintext), ciphertext) + + def test_decrypt_with_test_vector_3(self): + + key = bytearray(b'\x60\x3d\xeb\x10\x15\xca\x71\xbe' + b'\x2b\x73\xae\xf0\x85\x7d\x77\x81' + b'\x1f\x35\x2c\x07\x3b\x61\x08\xd7' + b'\x2d\x98\x10\xa3\x09\x14\xdf\xf4') + + plaintext = bytearray(b'\x60\x1e\xc3\x13\x77\x57\x89\xa5' + b'\xb7\xa7\xf5\x04\xbb\xf3\xd2\x28' + b'\xf4\x43\xe3\xca\x4d\x62\xb5\x9a' + b'\xca\x84\xe9\x90\xca\xca\xf5\xc5' + b'\x2b\x09\x30\xda\xa2\x3d\xe9\x4c' + b'\xe8\x70\x17\xba\x2d\x84\x98\x8d' + b'\xdf\xc9\xc5\x8d\xb6\x7a\xad\xa6' + b'\x13\xc2\xdd\x08\x45\x79\x41\xa6') + + ciphertext = bytearray(b'\x6b\xc1\xbe\xe2\x2e\x40\x9f' + b'\x96\xe9\x3d\x7e\x11\x73\x93' + b'\x17\x2a\xae\x2d\x8a\x57\x1e' + b'\x03\xac\x9c\x9e\xb7\x6f\xac' + b'\x45\xaf\x8e\x51\x30\xc8\x1c' + b'\x46\xa3\x5c\xe4\x11\xe5\xfb' + b'\xc1\x19\x1a\x0a\x52\xef\xf6' + b'\x9f\x24\x45\xdf\x4f\x9b\x17' + b'\xad\x2b\x41\x7b\xe6\x6c\x37\x10') + + counter = bytearray(b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8' + b'\xf9\xfa\xfb\xfc\xfd\xfe\xff') + + aesCTR = Python_AES_CTR(key, mode=6, IV=bytearray(b'\x00' * 12)) + aesCTR.counter = counter + self.assertEqual(aesCTR.decrypt(ciphertext), plaintext)