tlslite/utils/aesccm.py
# Copyright (c) 2019 Ivan Nikolchev
#
# See the LICENSE file for legal information regarding use of this file.
#
from __future__ import division
from tlslite.utils.cryptomath import numberToByteArray
from tlslite.utils import python_aes
class AESCCM(object):
# AES-CCM implementation per RFC3610
def __init__(self, key, implementation, rawAesEncrypt, tag_length=16):
self.isBlockCipher = False
self.isAEAD = True
self.key = key
self.tagLength = tag_length
self.nonceLength = 12
self.implementation = implementation
if len(self.key) == 16 and self.tagLength == 8:
self.name = "aes128ccm_8"
elif len(self.key) == 16 and self.tagLength == 16:
self.name = "aes128ccm"
elif len(self.key) == 32 and self.tagLength == 8:
self.name = "aes256ccm_8"
else:
assert len(self.key) == 32 and self.tagLength == 16
self.name = "aes256ccm"
self._ctr = python_aes.new(self.key, 6, bytearray(b'\x00' * 16))
self._cbc = python_aes.new(self.key, 2, bytearray(b'\x00' * 16))
def _cbcmac_calc(self, nonce, aad, msg):
L = 15 - len(nonce)
mac_data = bytearray()
# Flags constructed as in section 2.2 in the rfc
flags = 64 * (len(aad) > 0)
flags += 8 * ((self.tagLength - 2) // 2)
flags += 1 * (L - 1)
# Construct B_0
b_0 = bytearray([flags]) + nonce + numberToByteArray(len(msg), L)
aad_len_encoded = bytearray()
if len(aad) > 0:
if len(aad) < (2 ** 16 - 2 ** 8):
oct_size = 2
elif len(aad) < (2 ** 32):
oct_size = 4
aad_len_encoded = b'\xFF\xFE'
else:
oct_size = 8
aad_len_encoded = b'\xFF\xFF'
aad_len_encoded += numberToByteArray(len(aad), oct_size)
# Construct the bytearray that goes into the MAC
mac_data += b_0
mac_data += aad_len_encoded
mac_data += aad
# We need to pad with zeroes before and after msg blocks are added
self._pad_with_zeroes(mac_data, 16)
if msg != b'':
mac_data += msg
self._pad_with_zeroes(mac_data, 16)
# The mac data is now constructed and
# we need to run in through AES-CBC with 0 IV
self._cbc.IV = bytearray(b'\x00' * 16)
cbcmac = self._cbc.encrypt(mac_data)
# If the tagLength has default value 16, we return
# the whole last block. Otherwise we return only
# the first tagLength bytes from the last block
if self.tagLength == 16:
t = cbcmac[-16:]
else:
t = cbcmac[-16:-(16-self.tagLength)]
return t
def seal(self, nonce, msg, aad):
if len(nonce) != 12:
raise ValueError("Bad nonce length")
L = 15 - len(nonce)
# We construct the key stream blocks.
# S_0 is not used for encrypting the message, it is only used
# to compute the authentication value.
# S_1..S_n are used to encrypt the message.
flags = L - 1
s_0 = bytearray([flags]) + nonce + numberToByteArray(0, L)
mac = self._cbcmac_calc(nonce, aad, msg)
self._ctr.counter = s_0
if self.tagLength == 16:
auth_value = self._ctr.encrypt(mac)
else:
assert self.tagLength == 8
self._pad_with_zeroes(mac, 16)
auth_value = self._ctr.encrypt(mac)[:8]
enc_msg = self._ctr.encrypt(msg)
ciphertext = enc_msg + auth_value
return ciphertext
def open(self, nonce, ciphertext, aad):
if len(nonce) != 12:
raise ValueError("Bad nonce length")
if self.tagLength == 16 and len(ciphertext) < 16:
return None
if self.tagLength == 8 and len(ciphertext) < 8:
return None
L = 15 - len(nonce)
flags = L - 1
# Same construction as in seal function
s_0 = bytearray([flags]) + nonce + numberToByteArray(0, L)
auth_value = ciphertext[-self.tagLength:]
# We decrypt the auth value
self._ctr.counter = s_0
if self.tagLength == 16:
received_mac = self._ctr.decrypt(auth_value)
else:
assert self.tagLength == 8
self._pad_with_zeroes(auth_value, 16)
received_mac = self._ctr.decrypt(auth_value)[:8]
msg = self._ctr.decrypt(ciphertext)
msg = msg[:-self.tagLength]
computed_mac = self._cbcmac_calc(nonce, aad, msg)
# Compare the mac vlaue is the same as the one we computed
if received_mac != computed_mac:
return None
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