AES CCM是一种对数据进行加密及完整性检查的算法,主要用到AES中的CBC(完整性检查)和CTR(对明文进行加密),除此之外,还涉及到对数据的格式化(本文着重阐述)。
文章目录
- 加密过程
- STEPS
- 解密及校验过程
- STEPS
- 格式化
- B0的构成
- B0解析举例
- AAD的格式化
- Payload的格式化
- CTRn的组成
- 例子
- Python验证
加密过程
STEPS
从图中可以观察到,在加密开始时需要传入的有Nonce
, AAD(associated data)
,P(Payload或者叫明文)
,而从CCM加密流程出来后得到结果C
,由加密后的密文和加密后的TAG(或者叫MAC)
组成,具体的CBC和CTR是怎么回事在此文不做具体介绍,需要了解的可以看这里
解密及校验过程
STEPS
理解了加密过程后,看这个解密过程和校验MAC的过程就会简单不少,对于知道CBC和CTR的同学来说,上方的图不难理解,唯一的盲点在于如何将[N, A, P]这三者格式化成B0, B1, B2,…, Br,下面就详细谈谈这点
格式化
B0的构成
Flags
的组成:
Flags
由以下四部分组成:
- bit7 Reserved预留为以后扩展,设定为0
- Adata标记是否有无
associated data
,1代表有,0代表无 - bit3-bit5用于编码
TAG
(或者叫MAC)的长度,比如最终需要8字节的TAG,那么t=8,bit3-bit5被编码为011 - bit0-bit2用于编码
明文长度所占据的字节数
,比如q=2,即bit0-bit2编码为001,此时Q占据2字节,Q就是明文的长度,可表示范围0-65535
图中的N代表Nonce
,可以发现Nonce的长度和Q的长度相加等于15,这就代表Nonce的数据如果越少,那么可表示明文长度的范围越大,值得注意的是,000编码在Flags
的bit3-bit5和bit0-bit2均是不被允许的,即需要满足t >= 4, q >= 2, Len(Nonce) <= 13,CCM中各个长度需要满足的条件如下:
B0解析举例
AAD的格式化
在B0之后紧跟着的就是格式化后的AAD(如果B0 Flags中Adata存在) , 由AAD的长度
和AAD数据补0
构成
假设AAD的长度为a字节,对a的格式化如下:
如果a=2^16,那么编码为11111111 11111110 00000000 00000001 00000000 000000000,在此之后再连接AAD,连接完成后需要补0,补到啥效果呢?即补最少的0,达到格式化后的AAD长度是Block长度(16B)的倍数。也就是说,格式化后的AAD
可能会占据多个Block。
Payload的格式化
这个相对而言就比较简单了,就是在Payload后面补最少的0,使得格式化后的Payload长度是Block长度(16字节)的倍数
最终格式化后的数据为B0 || format(AAD) || format(Payload)
, 明显这最终的数据长度肯定是按Block长度对齐的,满足了CBC加密的要求,那CTR加密过程中的Ctr0, Ctr1,…是怎么构成的呢?
CTRn的组成
q
是前面B0中的那个q,代表Payload(明文)的长度所占据的空间,i就是Ctrn中的n,从0不断加1,即0,1,2,3…, N就是Nonce
例子
好了,到目前为止应该已经将AES CCM中的细节讲得差不多了,下面举个例子,可以对照着看是否理解上方的流程,如果需要更详细的资料,可以参考这里
Python验证
from Crypto.Cipher import AES
def aes_ccm_encrypt(key, plaintext, nonce, associated_data):
cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4)
cipher.update(associated_data)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
return ciphertext, tag
def aes_ccm_decrypt(key, ciphertext, tag, nonce, associated_data):
cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4)
cipher.update(associated_data)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
return plaintext
key = bytes([0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f])
nonce = bytes([0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16])
associated_data = bytes([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07])
plaintext = bytes([0x20, 0x21, 0x22, 0x23])
if __name__ == '__main__':
# 加密
ciphertext, tag = aes_ccm_encrypt(key, plaintext, nonce, associated_data)
print("Ciphertext:", ' '.join('{:02x}'.format(i) for i in ciphertext))
print("Tag:", ' '.join('{:02x}'.format(i) for i in tag))
# 解密及验证
try:
decrypted_plaintext = aes_ccm_decrypt(key, ciphertext, tag, nonce, associated_data)
print("Decrypted_plaintext:", ' '.join('{:02x}'.format(i) for i in decrypted_plaintext))
except (ValueError, KeyError) as e:
print("Decryption failed:", e)