赞
踩
了解分组密码的结构特点
掌握传统分组密码结构AES,以及AES在两种工作模式CBC和CTR下的实现
通过使用Python(推荐)或者C或者Java,编程分别实现CBC和CTR模式下的AES加密解密
实现两个加密/解密系统,
在密文分组链接模式(CBC)下使用AES
在计数器模式(CTR)中使用AES
完成程序后,使用附件的test.txt中给出的四组密钥和密文(十六进制形式)来验证你的代码
在两种模式下,16字节的加密IV都要求是随机生成的,并被添加到密文前面
对于CBC加密,要求使用PKCS5填充方案
对于AES的基本实现,你可以使用现有的加密库,如PyCrypto(Python),Crypto++(C++)或
任何其他语言和库;
要求自己实现CBC和CTR模式,而不是直接调用AES库的内置功能;
CBC模式简介
CBC - Cipher Block Chaining, 密码块链模式,在这种方法中,每个密文块都依赖于它前面的所有明文块。每个明分组先与前一个密文分组进行异或XOR后,再进行加密。为了保证每条消息的唯一性,在第一个分组中需要使用初始化向量IV(和第一个分组等长的随机比特序列)。它的实现机制使加密的各段数据之间有了联系,避免每个分组单独加密可能会出现前后两分组数据相同时密文分组相同的情况。
CBC模式下的AES加密过程
1、填充
CBC 是分组密码的一种工作模式,在加密前要对最后一块明文进行填充,实验要求使用 PKCS5 填充方案。
PKCS5:PKCS5是8字节填充的,即填充一定数量的内容,使得成为8的整数倍,而填充的内容取决于需要填充的数目。如果要填充 1 个字节,那填入的值就是 0x01;如果要填充 2 个字节,那么填入的值就是 0x02,以此类推。特殊情况下,若待加密数据长度已经满足了8的整倍数,则需要填入 8 个 0x08,目的是为了加解密时统一处理填充。
例如,串0x56在经过PKCS5填充之后会成为0x56 0x07 0x07 0x07 0x07 0x07 0x07 0x07因为需要填充7字节,因此填充的内容就是7。
PKCS7:PKCS7与PKCS5的区别在于PKCS5只填充到8字节,而PKCS7可以在1-255之间任意填充,可以说PKCS5是PKCS7的一种特例。
h<0x07><0x07><0x07><0x07><0x07><0x07><0x07> 7
he<0x06><0x06><0x06><0x06><0x06><0x06> 6
hel<0x05><0x05><0x05><0x05><0x05> 5
hell<0x04><0x04><0x04><0x04> 4
hello<0x03><0x03><0x03> 3
hello <0x02><0x02> 2
hello w<0x01> 1
hello wo<0x08><0x08><0x08><0x08><0x08><0x08><0x08><0x08> 8 // 数据块
hello wor<0x07><0x07><0x07><0x07><0x07><0x07><0x07> 7
hello word<0x06><0x06><0x06><0x06><0x06><0x06> 6
2、加密
由于当前数据分组的加密过程要使用前一分组加密后对应的密文分组,因此加密过程只能串行实现。
# 加密公式:
C1 = E(K,[P1 XOR IV])
CJ = E(K,[Pj XOR C(J-1)])
加密过程代码实现
def encrypt(self, plainText, iv): block_size = self.block_size aes_cipher = self.my_cipher plainText = bytes(plainText) iv = bytes(iv) # 待填充的长度 PKCS7填充规则 padding_size = block_size - (len(plainText) % block_size) # 待填充的字符 跟padding_size等长的padding_size plainText = plainText + bytes([padding_size] * padding_size) # 填充密文内容 # 初始化密文数组 IV + cipherText cipherText = bytearray(block_size + len(plainText)) # 把初始向量iv放入数组第一块密文处 cipherText[0:block_size] = iv # 从block_size处开始往后 以block_size步长顺次求出plainText每一块对应的cipherText for i in range(block_size, len(cipherText), block_size): # i记录每一块开始位置 j记录每一块长度 j = i + block_size # 使用operator.xor对前后块按位异或 # 由于密文空间给iv留出了第一块的空间,开始异或操作的位置应该为 i - block_size begin = i - block_size after_xor = bytes(map(operator.xor, plainText[begin:i], cipherText[begin:i])) # 对异或后的结果进行加密调用AES加密接口,并将结果放到cipherText对应位置 # 从IV后开始放 cipherText[i:j] = aes_cipher.encrypt(after_xor) return cipherText
3、解密
(1)获取IV,根据题目中要求,密文前16个字节是被随机生成的IV值
(2)去掉填充,加密进行了填充
(3)解密可以并行实现,因为C1,C2…Cn均为已知,两个输入的异或均可以同时得到
# 解密公式:
P1 = D(K,C1) XOR IV
Pj = D(K,Pj) XOR C(j-1)
# 并行
[P1,P2,.....PN] = [D(K,C1),D(K,C2),...,D(K,CN) XOR [IV,C1,...,CN-1]
def decrypt(self, cipherText): block_size = self.block_size aes_cipher = self.my_cipher cipherText = bytes(cipherText) # 解密可以并行实现,因为C1, C2…Cn均为已知,两个输入的异或均可以同时得到 # 解密 从IV后block_size处往后并行解密所有块 after_decrypt = aes_cipher.decrypt(cipherText[block_size:]) # 异或 从倒数第二个密文块开始和后面一个解密后的密文块并行异或、最后一个块是初始向量IV # 从-block_size出往前并行异或操作 blocks = map(operator.xor, after_decrypt, cipherText[:-block_size]) # 得到明文序列,按照PKCS7填充规则,计算填充的长度,并从末尾将填充块分离出去 plainText = bytes(blocks) # 返回最后的明文序列 return plainText[:-plainText[-1]]
实验结果
CTR模式简介
CTR模式全称CounTeR模式(计数器模式),是一种通过将逐次累加的计数器进行加密来生成密钥流的流密码。自增的算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次。
每次加密时都会生成一个不同的值(nonce)作为计数器的初始值。当分组长度为128比特时,计数器的初始值可能如下图:
加密过程中计数器的值会产生如下变化:
CTR模式的特点
(1)加密和解密使用了完全相同的模式,因此在程序上实现上比较容易。
(2)可以以任意顺序对分组进行加密和解密,因为在加密和解密时需要用到的“计数器”的值可以由nonce和分组序号直接计算出来。
(3)能够以任意顺序处理分组,意味着能够实现并行计算。在支持并行计算的系统中,CTR模式的速度是非常快的。
实验结果
CBC模式下的AES加密过程
1、加密
(1)用计数器的初始值,一次性计算各个计数器值
# 生成各个计数器的值
def _get_counts(self, iv, msgLen):
# iv: 计时器初值 msgLen: 密文长度(明文)
block_size = self.block_size
# 计算总共有多少个明文块 block_size - 1 为了准确计算块个数
blocks = int((msgLen + block_size - 1) // block_size)
# int.from_bytes 把bytes类型的变量x,转化为十进制整数
count = int.from_bytes(iv, 'big')
counts = iv
for i in range(1, blocks):
count += 1
# 是int.from_bytes的逆过程,把十进制整数,转换为bytes类型的格式
counts += count.to_bytes(block_size, 'big')
return counts
(2)并行加密
def encrypt(self, plainText, count):
# 计数器的基值
count = bytes(count)
# 一次性计算各个计数器值 用于后面的并行块加密
counters = self._get_counts(count, len(plainText))
# 对count值加密后和plainText异或 并行操作
blocks = map(operator.xor, self.cipher.encrypt(counters), plainText)
ciphertext = bytes(blocks)
return count + ciphertext[:len(plainText)]
2、解密
def decrypt(self, cipherText):
block_size = self.block_size
# 加密和解密公用同一套模式----都是加密操作 只有输入不同
# 用 密文 和 count加密操作得到最后的明文
pt = self.encrypt(cipherText[block_size:], cipherText[:block_size])
# 加密结果 把前面的count去掉就是所求的明文
return pt[block_size:]
python3.8 No module named ‘Crypto’ 问题,安装了crypto、pycryptodome 还是有问题。
解决方法:需要在当前项目里面把 \venv\Lib\site-packages下的crypto文件改名,直接改成Crypto,就能用了。
参考链接:https://baijiahao.baidu.com/s?id=1594440525549510453
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。