系列索引:【图解安全加密算法】加密算法系列索引 Python保姆级实现教程 | 物联网安全 | 信息安全
完整代码已更新
文章目录
- 一、AES的前世今生
- 二、AES简介
- 三、初始变换Initial round
- 四、字节代换SubBytes
- 五、行移位ShiftRows
- 六、列混合MixColumns
- 七、轮秘钥加AddRoundKey
- 秘钥扩展
- 八、流程回顾
- 九、完整代码
- 十、实验结果与心得体会
美国国家标准技术研究所在2001年发布了高级加密标准(AES)。AES
是一个对称加密算法,旨在取代DES成为广泛使用的标准,该标准以Rijndael算法为核心。
Rijndael算法是一种对称分组密码体制,采用代替或置换网络,每轮由三层组成:
AES标准规定Rijndael算法的分组长度为128位,而密钥长度可以为128、192或256位,相应的迭代轮数为10轮、12轮或14轮。Rijndael 汇聚了安全性能、效率、可实现性和灵活性等优点。Rijndael 对内存的需求低,使它很适合用于资源受限制的环境中,Rijndael 的操作简单,并可抵御强大和实时的攻击
AES,Advanced Encryption Standard,属于分组加密
算法,明文长度固定位128位
(16字节),秘钥长度可以是128,192,256
位,分别循环10、12、14轮
AES加密算法流程:
将明文转换后的结果和子秘钥矩阵进行异或
运算得到处理结果
def Init_round(self, plaintext, key):
self.plaintext_16 = np.array([_ for _ in plaintext.to_bytes(16, byteorder='big')]).reshape(4, 4).T
self.key_16 = np.array([_ for _ in key.to_bytes(16, byteorder='big')]).reshape(4, 4).T
return self.plaintext_16 ^ self.key_16
字节代替
:用一个S盒完成分组的字节到字节的代替;是一个基于S盒的非线性置换,它用于将每一个字节通过一个简单的查表操作映射为另一个字节。映射方法是把输入字节的高4位作为S盒的行值,低4位作为列值,然后取出S盒中对应行和列交叉位的元素作为输出
按照S盒尽心查询替换,这一步还是比较简单的,比如19->d4,是第1行第9列,查表可知结果为d4。
def SubBytes(self, arr):
return np.array([self.S_BOX[i][j] for i, j in [(_ >> 4, _ & 0xF) for _ in np.nditer(arr, order='F')]]).reshape(4, 4)
行移位
: AES 的行移位也是一个简单的左循环移位操作。当密钥长度为128比特时,状态矩阵的第0行左移0字节,第1行左移1字节,第2行左移2字节,第3行左移3字节
行移位也比较简单,就是第一行向左移
动一个字节,以此类推
def ShiftRows(self, arr):
S = np.zeros((4, 4), dtype='int')
for i in range(arr.shape[0]):
S[i] = np.roll(arr[i], -i)
return S
列混合
:列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵。
将输入的4x4矩阵左乘一个给定的4x4矩阵
乘法运算也不一样,这里需要按字节进行运算,01等于本身,故只需要关注02,03,04,下面分别是其运算公式
:
def MixColumns(self, arr):
M = np.zeros((4, 4), dtype=int)
for row in range(4):
for col in range(4):
for i in range(4):
M[row][col] ^= self.Mul(self.MIX_C[row][i], arr[i][col])
return M
轮密钥加
:当前分组和扩展密钥的一部分进行按位异或,将输入或中间态S的每一列与一个密钥字ki进行按位异或,即将128位轮密钥 Ki 同状态矩阵中的数据进行逐位异或操作。
def AddRoundKey(self, arr, i):
return arr ^ self.roundkey[:, (i-1)*4:(i-1)*4+4]
列混合得到的结果和轮秘钥进行异或运算,轮秘钥是由子秘钥矩阵扩展而来,下面来讲一下轮秘钥是怎么来的:
我们初始变换的到的是一个4x4的矩阵,比如我们接下来要求第i列,那么我们要看要求的这一列是否是4的倍数。
(1)如果i不是4的倍数,那么第i列由如下等式确定:
W [ i ] = W [ i − 4 ] ⨁ W [ i − 1 ] W[i]=W[i-4]⨁W[i-1] W[i]=W[i−4]⨁W[i−1]
(2)如果i是4的倍数,那么第i列由如下等式确定:
W [ i ] = W [ i − 4 ] ⨁ T ( W [ i − 1 ] ) W[i]=W[i-4]⨁T(W[i-1]) W[i]=W[i−4]⨁T(W[i−1])
比如第5列(列数从0开始计),不是4的倍数,则用(1)中的公式计算即可,即第1列和第4列异或
当i是4的倍数时,要经过T函数
的处理,T函数由三部分组成,分别是字循环、字节代换和轮常量异或。
a.字循环
将1个字中的4个字节循环左移1个字节。即将输⼊字[b0, b1, b2, b3]变换成[b1,b2,b3,b0]。
b.字节代换
对字循环的结果使⽤S盒进⾏字节代换。
c.轮常量异或
将前两步的结果同轮常量Rcon[j]进⾏异或,其中j表示轮数。
这里的Rcon轮常量
是给定的,与w[i-4]和字节代换后的结果进行异或运算得到最终的w[i]。
def Round_key(self, arr):
M = np.hstack((arr, np.zeros((4, 40), dtype=int)))
for i in range(4, 44):
M[:, i] = M[:, i-4] ^ M[:, i - 1] if i % 4 else M[:,i-4] ^ self.T(M[:, i-1], i//4-1)
return M[:, 4:]
这里仅给出加密过程,有能力的同学可以对照着写解密
import numpy as np
class AES:
MIX_C = np.array([[0x2, 0x3, 0x1, 0x1], [0x1, 0x2, 0x3, 0x1], [0x1, 0x1, 0x2, 0x3], [0x3, 0x1, 0x1, 0x2]])
RCon = np.array([[0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36],
[0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
[0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
[0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]])
S_BOX = [[0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76],
[0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,
0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0],
[0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC,
0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15],
[0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A,
0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75],
[0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0,
0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84],
[0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B,
0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF],
[0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85,
0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8],
[0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,
0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2],
[0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17,
0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73],
[0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88,
0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB],
[0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C,
0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79],
[0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9,
0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08],
[0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6,
0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A],
[0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E,
0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E],
[0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94,
0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF],
[0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x1]]
def __init__(self, plaintext, key):
self.plaintext = plaintext
self.key = key
self.plaintext_16 = None
self.key_16 = None
self.roundkey = None
def Init_round(self, plaintext, key):
self.plaintext_16 = np.array(
[_ for _ in plaintext.to_bytes(16, byteorder='big')]).reshape(4, 4).T
self.key_16 = np.array([_ for _ in key.to_bytes(
16, byteorder='big')]).reshape(4, 4).T
return self.plaintext_16 ^ self.key_16
def SubBytes(self, arr):
return np.array([self.S_BOX[i][j] for i, j in [(_ >> 4, _ & 0xF) for _ in np.nditer(arr, order='F')]]).reshape(4, 4)
def ShiftRows(self, arr):
S = np.zeros((4, 4), dtype='int')
for i in range(arr.shape[0]):
S[i] = np.roll(arr[i], -i)
return S
def Mul(self, x1, x2):
if x1 == 0x1:
x = x2
elif x1 == 0x2:
x = ((x2 << 1 & 0xff) ^ 0b00011011) if (
x2 & 0x80) else (x2 << 1 & 0xff)
elif x1 == 0x3:
x = self.Mul(0x2, x2) ^ x2
elif x1 == 0x4:
x = self.Mul(Mul(0x2, 0x2), x2)
return x
def MixColumns(self, arr):
M = np.zeros((4, 4), dtype=int)
for row in range(4):
for col in range(4):
for i in range(4):
M[row][col] ^= self.Mul(self.MIX_C[row][i], arr[i][col])
return M
def T(self, arr, i):
arr = np.roll(arr, -1)
M = np.array([self.S_BOX[i][j] for i, j in [(_ >> 4, _ & 0xF) for _ in arr]])
M = M ^ self.RCon[:, i]
return M
def Round_key(self, arr):
M = np.hstack((arr, np.zeros((4, 40), dtype=int)))
for i in range(4, 44):
M[:, i] = M[:, i-4] ^ M[:, i - 1] if i % 4 else M[:,i-4] ^ self.T(M[:, i-1], i//4-1)
return M[:, 4:]
def AddRoundKey(self, arr, i):
return arr ^ self.roundkey[:, (i-1)*4:(i-1)*4+4]
def aes_encrypt(self):
State = self.Init_round(self.plaintext, self.key)
self.roundkey = self.Round_key(self.key_16)
for r in range(1, 10):
State = self.SubBytes(State)
State = self.ShiftRows(State)
State = self.MixColumns(State)
State = self.AddRoundKey(State, r)
State = self.SubBytes(State)
State = self.ShiftRows(State)
State = self.AddRoundKey(State, 10)
return State
if __name__ == '__main__':
plaintext = 0x00112233445566778899aabbccddeeff
key = 0x000102030405060708090a0b0c0d0e0f
aes = AES(plaintext, key)
print(aes.aes_encrypt())
# print(arr)
# print(aes.ShiftRows(arr))
# print(aes.SubBytes(arr))
# arr = np.array([[0xd4, 0xe0, 0xb8, 0x1e], [0xbf, 0xb4, 0x41, 0x27], [0x5d, 0x52, 0x11, 0x98], [0x30, 0xae, 0xf1, 0xe5]])
# print(aes.MixColumns(arr))
# arr= np.array([[0x2b, 0x28, 0xab, 0x09], [0x7e, 0xae, 0xf7, 0xcf], [0x15, 0xd2, 0x15, 0x4f], [0x16, 0xa6, 0x88, 0x3c]])
# print(aes.Round_key(arr))
将结果储存在了一个矩阵里,结果用int型表示。(这个实验是这学期第一次写加密算法,所以代码写的比较稚嫩,各位大佬见谅,中间变量没有处理成二进制或16进制,大佬可在此基础上改进)
心得:
不得不说,AES理论理解起来并不是特别难,但当真正去动手复现算法,确是一头雾水,在机房的时候感觉无从下手,回去之后又把课本认真看了看,搞清楚了各个步骤之间的衔接和逻辑,然后开始了代码设计,我选择的是Python,因为了解过一些用于科学计算的库,这里我就使用了numpy。这个实验我真是从0开始一行一行实现各个函数的功能,其间的坎坷让我加深了对AES的理解,先是把各个步骤的代码实现并进行验证,然后再写一个函数将他们串联起来,这个实验我写了两天,虽然中间也有其他事情要处理,不过当最后完整验算过一遍之后,内心十分开心,觉得努力没有白费!
图解安全加密算法系列持续更新,欢迎
点赞收藏
+关注
上一篇:【图解DSA数字签名算法】DSA签名算法的Python实现 | 物联网安全 | 信息安全
下一篇:【图解RSA加密算法】RSA非对称密码算法的Python实现保姆级教程 | 物联网安全 | 信息安全
本人水平有限,文章中不足之处欢迎下方评论区批评指正~如果感觉对你有帮助,点个赞 支持一下吧 ~
不定期分享 有趣、有料、有营养内容,欢迎 订阅关注 我的博客 ,期待在这与你相遇 ~