Skip to content

Commit cfdfb7c

Browse files
authored
Merge pull request #384 from inikolcev/separate-aes-ctr
Separate CTR code and replace it in AES-CCM and AES-GCM
2 parents 27157e0 + 84c57db commit cfdfb7c

14 files changed

+920
-91
lines changed

tests/tlstest.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -921,10 +921,13 @@ def connect():
921921
continue
922922
if cipher in ("aes128gcm", "aes256gcm") and \
923923
implementation not in ("pycrypto",
924-
"python"):
924+
"python", "openssl"):
925925
continue
926-
if cipher in ("chacha20-poly1305_draft00", "chacha20-poly1305",
927-
"aes128ccm", "aes128ccm_8", "aes256ccm", "aes256ccm_8") \
926+
if cipher in ("aes128ccm", "aes128ccm_8",
927+
"aes256ccm", "aes256ccm_8") and \
928+
implementation not in ("python", "openssl"):
929+
continue
930+
if cipher in ("chacha20-poly1305_draft00", "chacha20-poly1305") \
928931
and implementation not in ("python", ):
929932
continue
930933

@@ -2211,11 +2214,13 @@ def server_bind(self):
22112214
continue
22122215
if cipher in ("aes128gcm", "aes256gcm") and \
22132216
implementation not in ("pycrypto",
2214-
"python"):
2217+
"python", "openssl"):
2218+
continue
2219+
if cipher in ("aes128ccm", "aes128ccm_8",
2220+
"aes256ccm", "aes256ccm_8") and \
2221+
implementation not in ("python", "openssl"):
22152222
continue
2216-
if cipher in ("chacha20-poly1305_draft00", "chacha20-poly1305",
2217-
"aes128ccm", "aes128ccm_8",
2218-
"aes256ccm", "aes256ccm_8") \
2223+
if cipher in ("chacha20-poly1305_draft00", "chacha20-poly1305") \
22192224
and implementation not in ("python", ):
22202225
continue
22212226

tlslite/utils/aes.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ class AES(object):
77
def __init__(self, key, mode, IV, implementation):
88
if len(key) not in (16, 24, 32):
99
raise AssertionError()
10-
if mode != 2:
11-
raise AssertionError()
12-
if len(IV) != 16:
10+
if mode not in [2, 6]:
1311
raise AssertionError()
12+
if mode == 2:
13+
if len(IV) != 16:
14+
raise AssertionError()
15+
if mode == 6:
16+
if len(IV) > 16:
17+
raise AssertionError()
1418
self.isBlockCipher = True
1519
self.isAEAD = False
1620
self.block_size = 16

tlslite/utils/aesccm.py

Lines changed: 19 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
#
55

66
from __future__ import division
7-
from tlslite.utils.cryptomath import numberToByteArray, divceil
8-
from tlslite.utils.python_aes import Python_AES
9-
import sys
10-
import array
7+
from tlslite.utils.cryptomath import numberToByteArray
8+
from tlslite.utils import python_aes
119

1210

1311
class AESCCM(object):
@@ -18,6 +16,9 @@ def __init__(self, key, implementation, rawAesEncrypt, tag_length=16):
1816
self.isAEAD = True
1917
self.key = key
2018
self.tagLength = tag_length
19+
self.nonceLength = 12
20+
self.implementation = implementation
21+
2122
if len(self.key) == 16 and self.tagLength == 8:
2223
self.name = "aes128ccm_8"
2324
elif len(self.key) == 16 and self.tagLength == 16:
@@ -27,10 +28,10 @@ def __init__(self, key, implementation, rawAesEncrypt, tag_length=16):
2728
else:
2829
assert len(self.key) == 32 and self.tagLength == 16
2930
self.name = "aes256ccm"
30-
self._rawAesEncrypt = rawAesEncrypt
31-
self.implementation = implementation
32-
self.nonceLength = 12
33-
self._cbc = Python_AES(self.key, 2, bytearray(b'\x00' * 16))
31+
32+
self._ctr = python_aes.new(self.key, 6, bytearray(b'\x00' * 16))
33+
self._cbc = python_aes.new(self.key, 2, bytearray(b'\x00' * 16))
34+
3435

3536
def _cbcmac_calc(self, nonce, aad, msg):
3637
L = 15 - len(nonce)
@@ -89,25 +90,19 @@ def seal(self, nonce, msg, aad):
8990
raise ValueError("Bad nonce length")
9091

9192
L = 15 - len(nonce)
92-
auth_value = bytearray(self.tagLength)
9393

9494
# We construct the key stream blocks.
9595
# S_0 is not used for encrypting the message, it is only used
9696
# to compute the authentication value.
9797
# S_1..S_n are used to encrypt the message.
9898

9999
flags = L - 1
100-
s_0 = self._rawAesEncrypt(bytearray([flags]) +
101-
nonce + numberToByteArray(0, L))
102-
103-
s_n = self._construct_s_n(msg, flags, nonce, L)
104-
105-
enc_msg = self._xor(msg, s_n)
100+
s_0 = bytearray([flags]) + nonce + numberToByteArray(0, L)
106101

107102
mac = self._cbcmac_calc(nonce, aad, msg)
108-
109-
for i in range(self.tagLength):
110-
auth_value[i] = mac[i] ^ s_0[i]
103+
self._ctr.counter = s_0
104+
auth_value = self._ctr.encrypt(mac)
105+
enc_msg = self._ctr.encrypt(msg)
111106

112107
ciphertext = enc_msg + auth_value
113108
return ciphertext
@@ -122,61 +117,29 @@ def open(self, nonce, ciphertext, aad):
122117
return None
123118

124119
L = 15 - len(nonce)
125-
received_mac = bytearray(self.tagLength)
126120
flags = L - 1
127121

128122
# Same construction as in seal function
129123

130-
s_0 = self._rawAesEncrypt(bytearray([flags]) +
131-
nonce + numberToByteArray(0, L))
124+
s_0 = bytearray([flags]) + nonce + numberToByteArray(0, L)
132125

133-
s_n = self._construct_s_n(ciphertext, flags, nonce, L)
126+
auth_value = ciphertext[-self.tagLength:]
134127

135-
msg = self._xor(ciphertext, s_n)
128+
# We decrypt the auth value
129+
self._ctr.counter = s_0
130+
received_mac = self._ctr.decrypt(auth_value)
131+
msg = self._ctr.decrypt(ciphertext)
136132
msg = msg[:-self.tagLength]
137-
138-
auth_value = ciphertext[-self.tagLength:]
139133
computed_mac = self._cbcmac_calc(nonce, aad, msg)
140134

141-
# We decrypt the auth value
142-
for i in range(self.tagLength):
143-
received_mac[i] = auth_value[i] ^ s_0[i]
144135

145136
# Compare the mac vlaue is the same as the one we computed
146137
if received_mac != computed_mac:
147138
return None
148139
return msg
149140

150-
def _construct_s_n(self, ciphertext, flags, nonce, L):
151-
s_n = bytearray()
152-
counter_lmt = divceil(len(ciphertext), 16)
153-
for i in range(1, int(counter_lmt) + 1):
154-
s_n += self._rawAesEncrypt(bytearray([flags]) +
155-
nonce + numberToByteArray(i, L))
156-
return s_n
157-
158-
if sys.version_info[0] >= 3:
159-
def _xor(self, inp, s_n):
160-
inp_added = -((8 - (len(inp) % 8)) % 8) or None
161-
self._pad_with_zeroes(inp, 8)
162-
msg = self._use_memoryview(inp, s_n)[:inp_added]
163-
inp[:] = inp[:inp_added]
164-
return msg
165-
else:
166-
def _xor(self, inp, s_n):
167-
msg = bytearray(i ^ j for i, j in zip(inp, s_n))
168-
return msg
169-
170141
@staticmethod
171142
def _pad_with_zeroes(data, size):
172143
if len(data) % size != 0:
173144
zeroes_to_add = size - (len(data) % size)
174145
data += b'\x00' * zeroes_to_add
175-
176-
@staticmethod
177-
def _use_memoryview(msg, s_n):
178-
msg_mv = memoryview(msg).cast('Q')
179-
s_n_mv = memoryview(s_n).cast('Q')
180-
enc_arr = array.array('Q', (i ^ j for i, j in zip(msg_mv, s_n_mv)))
181-
enc_msg = bytearray(enc_arr.tobytes())
182-
return enc_msg

tlslite/utils/aesgcm.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# look-up table.
1515

1616
from __future__ import division
17+
from tlslite.utils import python_aes
1718
from .constanttime import ct_compare_digest
1819
from .cryptomath import bytesToNumber, numberToByteArray
1920

@@ -35,8 +36,10 @@ def __init__(self, key, implementation, rawAesEncrypt):
3536
self.name = "aes256gcm"
3637
else:
3738
raise AssertionError()
39+
self.key = key
3840

3941
self._rawAesEncrypt = rawAesEncrypt
42+
self._ctr = python_aes.new(self.key, 6, bytearray(b'\x00' * 16))
4043

4144
# The GCM key is AES(0).
4245
h = bytesToNumber(self._rawAesEncrypt(bytearray(16)))
@@ -53,18 +56,6 @@ def __init__(self, key, implementation, rawAesEncrypt):
5356
self._productTable[self._reverseBits(i+1)] = \
5457
self._gcmAdd(self._productTable[self._reverseBits(i)], h)
5558

56-
def _rawAesCtrEncrypt(self, counter, inp):
57-
"""
58-
Encrypts (or decrypts) plaintext with AES-CTR. counter is modified.
59-
"""
60-
out = bytearray(len(inp))
61-
rawAesEncrypt = self._rawAesEncrypt
62-
for i in range(0, len(out), 16):
63-
mask = rawAesEncrypt(counter)
64-
for j in range(i, min(len(out), i + 16)):
65-
out[j] = inp[j] ^ mask[j-i]
66-
self._inc32(counter)
67-
return out
6859

6960
def _auth(self, ciphertext, ad, tagMask):
7061
y = 0
@@ -125,7 +116,8 @@ def seal(self, nonce, plaintext, data):
125116

126117
# The counter starts at 2 for the actual encryption.
127118
counter[-1] = 2
128-
ciphertext = self._rawAesCtrEncrypt(counter, plaintext)
119+
self._ctr.counter = counter
120+
ciphertext = self._ctr.encrypt(plaintext)
129121

130122
tag = self._auth(ciphertext, data, tagMask)
131123

@@ -158,7 +150,8 @@ def open(self, nonce, ciphertext, data):
158150

159151
# The counter starts at 2 for the actual decryption.
160152
counter[-1] = 2
161-
return self._rawAesCtrEncrypt(counter, ciphertext)
153+
self._ctr.counter = counter
154+
return self._ctr.decrypt(ciphertext)
162155

163156
@staticmethod
164157
def _reverseBits(i):

tlslite/utils/cipherfactory.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from tlslite.utils import python_chacha20_poly1305
1212
from tlslite.utils import python_rc4
1313
from tlslite.utils import python_tripledes
14+
from tlslite.utils import openssl_aesccm
15+
from tlslite.utils import openssl_aesgcm
1416

1517
from tlslite.utils import cryptomath
1618

@@ -53,7 +55,27 @@ def createAES(key, IV, implList=None):
5355
elif impl == "pycrypto" and cryptomath.pycryptoLoaded:
5456
return pycrypto_aes.new(key, 2, IV)
5557
elif impl == "python":
56-
return python_aes.new(key, 2, IV)
58+
return python_aes.new(key, 2, IV)
59+
raise NotImplementedError()
60+
61+
def createAESCTR(key, IV, implList=None):
62+
"""Create a new AESCTR object.
63+
64+
:type key: str
65+
:param key: A 16, 24, or 32 byte string.
66+
67+
:type IV: str
68+
:param IV: A 8 or 12 byte string
69+
70+
:rtype: tlslite.utils.AES
71+
:returns: An AES object.
72+
"""
73+
if implList is None:
74+
implList = ["python"]
75+
76+
for impl in implList:
77+
if impl == "python":
78+
return python_aes.new(key, 6, IV)
5779
raise NotImplementedError()
5880

5981
def createAESGCM(key, implList=None):
@@ -66,9 +88,11 @@ def createAESGCM(key, implList=None):
6688
:returns: An AESGCM object.
6789
"""
6890
if implList is None:
69-
implList = ["pycrypto", "python"]
91+
implList = ["openssl", "pycrypto", "python"]
7092

7193
for impl in implList:
94+
if impl == "openssl" and cryptomath.m2cryptoLoaded:
95+
return openssl_aesgcm.new(key)
7296
if impl == "pycrypto" and cryptomath.pycryptoLoaded:
7397
return pycrypto_aesgcm.new(key)
7498
if impl == "python":
@@ -86,9 +110,11 @@ def createAESCCM(key, implList=None):
86110
"""
87111

88112
if implList is None:
89-
implList = ["python"]
113+
implList = ["openssl", "python"]
90114

91115
for impl in implList:
116+
if impl == "openssl" and cryptomath.m2cryptoLoaded:
117+
return openssl_aesccm.new(key)
92118
if impl == "python":
93119
return python_aesccm.new(key)
94120

@@ -105,9 +131,11 @@ def createAESCCM_8(key, implList=None):
105131
"""
106132

107133
if implList is None:
108-
implList = ["python"]
134+
implList = ["openssl", "python"]
109135

110136
for impl in implList:
137+
if impl == "openssl" and cryptomath.m2cryptoLoaded:
138+
return openssl_aesccm.new(key, 8)
111139
if impl == "python":
112140
return python_aesccm.new(key, 8)
113141

tlslite/utils/openssl_aes.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111
def new(key, mode, IV):
1212
# IV argument name is a part of the interface
1313
# pylint: disable=invalid-name
14-
return OpenSSL_AES(key, mode, IV)
14+
if mode == 2:
15+
return OpenSSL_AES(key, mode, IV)
16+
elif mode == 6:
17+
return OpenSSL_CTR(key, mode, IV)
18+
else:
19+
raise NotImplementedError()
20+
1521

1622
class OpenSSL_AES(AES):
1723

@@ -58,3 +64,53 @@ def decrypt(self, ciphertext):
5864
def __del__(self):
5965
if self._context is not None:
6066
m2.cipher_ctx_free(self._context)
67+
68+
69+
class OpenSSL_CTR(AES):
70+
71+
def __init__(self, key, mode, IV):
72+
# IV argument/field names are a part of the interface
73+
# pylint: disable=invalid-name
74+
AES.__init__(self, key, mode, IV, "openssl")
75+
self._IV = IV
76+
self.key = key
77+
self._context = None
78+
self._encrypt = None
79+
if len(key) not in (16, 24, 32):
80+
raise AssertionError()
81+
82+
@property
83+
def counter(self):
84+
return self._IV
85+
86+
@counter.setter
87+
def counter(self, ctr):
88+
if self._context is not None:
89+
m2.cipher_ctx_free(self._context)
90+
self._IV = ctr
91+
self._init_context()
92+
93+
def _init_context(self, encrypt=True):
94+
if len(self.key) == 16:
95+
cipherType = m2.aes_128_ctr()
96+
if len(self.key) == 24:
97+
cipherType = m2.aes_192_ctr()
98+
if len(self.key) == 32:
99+
cipherType = m2.aes_256_ctr()
100+
self._context = m2.cipher_ctx_new()
101+
m2.cipher_init(self._context, cipherType, self.key, self._IV,
102+
int(encrypt))
103+
m2.cipher_set_padding(self._context, 0)
104+
self._encrypt = encrypt
105+
106+
def encrypt(self, plaintext):
107+
ciphertext = m2.cipher_update(self._context, plaintext)
108+
return bytearray(ciphertext)
109+
110+
def decrypt(self, ciphertext):
111+
plaintext = m2.cipher_update(self._context, ciphertext)
112+
return bytearray(plaintext)
113+
114+
def __del__(self):
115+
if self._context is not None:
116+
m2.cipher_ctx_free(self._context)

0 commit comments

Comments
 (0)