Skip to content

Commit a406e79

Browse files
author
Ivan Nikolchev
committed
AES-CCM implementation per rfc3610
1 parent 322f5ff commit a406e79

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

tlslite/utils/aesccm.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Copyright (c) 2019 Ivan Nikolchev
2+
#
3+
# See the LICENSE file for legal information regarding use of this file.
4+
#
5+
6+
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
11+
12+
13+
class AESCCM(object):
14+
# AES-CCM implementation per RFC3610
15+
16+
def __init__(self, key, implementation, rawAesEncrypt, tag_length=16):
17+
self.isBlockCipher = False
18+
self.isAEAD = True
19+
self.key = key
20+
self.tagLength = tag_length
21+
if len(self.key) == 16 and self.tagLength == 8:
22+
self.name = "aes128ccm_8"
23+
elif len(self.key) == 16 and self.tagLength == 16:
24+
self.name = "aes128ccm"
25+
elif len(self.key) == 32 and self.tagLength == 8:
26+
self.name = "aes256ccm_8"
27+
else:
28+
assert len(self.key) == 32 and self.tagLength == 16
29+
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))
34+
35+
def _cbcmac_calc(self, nonce, aad, msg):
36+
L = 15 - len(nonce)
37+
mac_data = bytearray()
38+
39+
# Flags constructed as in section 2.2 in the rfc
40+
flags = 64 * (len(aad) > 0)
41+
flags += 8 * ((self.tagLength - 2) // 2)
42+
flags += 1 * (L - 1)
43+
44+
# Construct B_0
45+
b_0 = bytearray([flags]) + nonce + numberToByteArray(len(msg), L)
46+
47+
aad_len_encoded = bytearray()
48+
if len(aad) > 0:
49+
if len(aad) < (2 ** 16 - 2 ** 8):
50+
oct_size = 2
51+
elif len(aad) < (2 ** 32):
52+
oct_size = 4
53+
aad_len_encoded = b'\xFF\xFE'
54+
else:
55+
oct_size = 8
56+
aad_len_encoded = b'\xFF\xFF'
57+
58+
aad_len_encoded += numberToByteArray(len(aad), oct_size)
59+
60+
# Construct the bytearray that goes into the MAC
61+
mac_data += b_0
62+
mac_data += aad_len_encoded
63+
mac_data += aad
64+
65+
# We need to pad with zeroes before and after msg blocks are added
66+
self._pad_with_zeroes(mac_data, 16)
67+
if msg != b'':
68+
mac_data += msg
69+
self._pad_with_zeroes(mac_data, 16)
70+
71+
# The mac data is now constructed and
72+
# we need to run in through AES-CBC with 0 IV
73+
74+
self._cbc.IV = bytearray(b'\x00' * 16)
75+
cbcmac = self._cbc.encrypt(mac_data)
76+
77+
# If the tagLength has default value 16, we return
78+
# the whole last block. Otherwise we return only
79+
# the first tagLength bytes from the last block
80+
if self.tagLength == 16:
81+
t = cbcmac[-16:]
82+
else:
83+
t = cbcmac[-16:-(16-self.tagLength)]
84+
return t
85+
86+
def seal(self, nonce, msg, aad):
87+
88+
if len(nonce) != 12:
89+
raise ValueError("Bad nonce length")
90+
91+
L = 15 - len(nonce)
92+
auth_value = bytearray(self.tagLength)
93+
94+
# We construct the key stream blocks.
95+
# S_0 is not used for encrypting the message, it is only used
96+
# to compute the authentication value.
97+
# S_1..S_n are used to encrypt the message.
98+
99+
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)
106+
107+
mac = self._cbcmac_calc(nonce, aad, msg)
108+
109+
for i in range(self.tagLength):
110+
auth_value[i] = mac[i] ^ s_0[i]
111+
112+
ciphertext = enc_msg + auth_value
113+
return ciphertext
114+
115+
def open(self, nonce, ciphertext, aad):
116+
117+
if len(nonce) != 12:
118+
raise ValueError("Bad nonce length")
119+
if self.tagLength == 16 and len(ciphertext) < 16:
120+
return None
121+
if self.tagLength == 8 and len(ciphertext) < 8:
122+
return None
123+
124+
L = 15 - len(nonce)
125+
received_mac = bytearray(self.tagLength)
126+
flags = L - 1
127+
128+
# Same construction as in seal function
129+
130+
s_0 = self._rawAesEncrypt(bytearray([flags]) +
131+
nonce + numberToByteArray(0, L))
132+
133+
s_n = self._construct_s_n(ciphertext, flags, nonce, L)
134+
135+
msg = self._xor(ciphertext, s_n)
136+
msg = msg[:-self.tagLength]
137+
138+
auth_value = ciphertext[-self.tagLength:]
139+
computed_mac = self._cbcmac_calc(nonce, aad, msg)
140+
141+
# We decrypt the auth value
142+
for i in range(self.tagLength):
143+
received_mac[i] = auth_value[i] ^ s_0[i]
144+
145+
# Compare the mac vlaue is the same as the one we computed
146+
if received_mac != computed_mac:
147+
return None
148+
return msg
149+
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+
170+
@staticmethod
171+
def _pad_with_zeroes(data, size):
172+
if len(data) % size != 0:
173+
zeroes_to_add = size - (len(data) % size)
174+
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

0 commit comments

Comments
 (0)