Featured image of post AES加密算法

AES加密算法

众所周知,AES 算法是我们在安卓逆向中的一个比较重要的算法,掌握它的算法细节对于我们逆向无疑是有很大的帮助的
这一块呢,看的是龙哥的文章来学习的,为了记个笔记,就照搬了一下龙哥的图,致敬大佬

AES 分类

分类 密钥长度 轮密钥长度 扩展密钥长度 分组长度 加密轮数
AES-128 16 字节 16 字节 16*11=172 16 字节 10
AES-192 24 字节 16 字节 16*13=208 16 字节 12
AES-256 32 字节 16 字节 16*15=240 16 字节 14

上面的这三个算法,除了密钥编排算法不同和加密的轮数不一样之外,其余的计算逻辑都是相同的
CBC 模式 IV 都是 16 个字节,CBC 计算逻辑相同

  • AES-128 密钥编排算法中 K0 是原始密钥,共 16 个字节
  • AES-192 密钥编排算法中 K0 和 K1 的前半部分是原始密钥,共 24 个字节
  • AES-256 密钥编排算法中 K0 和 K1 是原始密钥,共 32 字节

填充模式

在 AES 中,识别常见的明文填充模式也是比较重要的,下面是几种常见的填充模式:

PKCS #7 Padding

这个是目前最标准,最通用的填充方式

  • 规则:假设需要填充 N 个字节,则填充的 N 个字节的值都是 N(16 进制)
  • 特殊情况:如果明文长度正好是 16 字节的整数倍,则额外填充一个完整的块(即 16 个字节,每个字节都是 0x10,即十进制的 16),以便解密时能正确的移除填充
  • 示例:
    • 缺 3 个字节: 03 03 03
    • 缺 1 个字节: 01
    • 正好整除: 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10

PKCS #5 Padding

PKCS #5 标准严格规定分组长度 8 字节(64 位)

  • 计算缺失长度:计算明文距离下一个 8 字节倍数还差多少字节,记为 N
  • 填充内容:在明文末尾添加 N 个字节,每个字节的值都等于 N(16 进制表示)
  • 特殊情况:如果明文长度正好是 8 字节的整数倍,则额外填充一个完整的块(即填充 8 个字节,每个字节的值都是 0x08)
  • 示例:
    • 明文长度不足 8 个字节
      • 明文: A B C D E (5 字节)
      • 填充: A B C D E 03 03 03
    • 明文长度超过 8 个字节但未满 16 字节
      • 明文: A B C D E F G H I J (10 字节)
      • 填充: A B C D E F G H I J 06 06 06 06 06 06
    • 明文长度正好是 8 的倍数
      • 明文: A B C D E F G H (8 字节)
      • 填充: A B C D E F G H 08 08 08 08 08 08 08 08

Zero Padding(零填充)

直接填充 0,缺 3 个字节则填充 00 00 00

No Padding

  • 规则:不进行任何填充
  • 使用条件:
    • 明文长度严格等于 16 的倍数
    • 或者使用了不需要填充的工作模式

工作模式

下面我总结成了一个表

模式 是否需要填充 是否需要 iv
ECB
CBC
CTR
GCM
CFB
OFB

计算流程

下面主要以 AES-128 为例,其他的也是一样的
AES-128 接收 16 字节的明文输入,16 字节的密钥,输出 16 字节的密文结果
设置 key 为 2b7e151628aed2a6abf7158809cf4f3c,明文为 00112233445566778899aabbccddeeff
AES 的整体可以分成左右两块,即明文的处理和密钥的编排
明文的处理主体是一个初始化轮密钥加和十轮运算,在初始化轮密钥加十轮运算中都需要使用密钥编排的结果
密钥编排将 16 个字节经过运算推演出 11 组轮密钥,每一组 16 个字节,称之为 K0,K1…K10

密钥编排

首先来看一下密钥是如何算出来扩展密钥的,上面设置的 key 是 2b7e151628aed2a6abf7158809cf4f3c,为了区分密钥和密钥编排后的轮密钥,我们将此时的密钥叫做主密钥
在 AES-128 中,密钥扩展后得到 $17 \times 16=176$ 字节, 使用时逐 16 字节划分成 $K_0,K_1,……,K_{10}$,但是在生成时,它是逐 4 字节生成的,即 44*4。不妨用数组来描述它,即一个包含了 44 个元素的数组,叫 $W$
这 44 个元素生成的规则有三种,如下图所示:

不同颜色代表了不同规则,最上方蓝色区域的就是主密钥本身切成四段
左侧的红色部分,$W_4,W_8,W_{12},……,W_{40}$ 的生成稍复杂一点

$$ W_n= g(W_{n-1})\quad xor\quad W_{n-4} $$

xor 是异或运算,比如: $W_4 = g(W_3)\quad xor\quad W_0$ 。g(当前元素前面那个元素)异或当前元素头顶上那个元素
那么关键点就是这个 g 函数了,g 函数一个分三个步骤–循环左移,S 盒替换,字节异或,下面以 $W_4$ 运算中所需的 $W_3$ 为例。

$$ W_3=09cf4f3c\\ $$

首先是循环左移,规则固定–将最左边的一个字节挪到右边即可

第二步就是 S 盒替换,将数值本身作为索引取出 S 数组中对应的值。S 盒是固定的,在逆向的过程中经常会用来判断 AES 的标志

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SBox = [
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, 0x16
]

num = 0x13
result = SBox[num]
print(hex(result))
### 7d

虽然 S 盒背后有十分复杂的知识,但是我们逆向算法,一般用不着去了解

最后一个步骤简单,将上一步得到的结果中的最高字节与一个固定常量异或。$W_4$ 的生成是第一个,用如下 rcon 表的第一个元素 0x01。$W_{40}$ 即第 10 次,用最后一个元素 0x36

1
rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]

最终结果

下面红色的部分同理
接下来就应该看一下橙色的部分了,和红色的部分类似,去掉 g 函数即可

例如: $W_5 = W_4\quad xor\quad W_1=0xa0fafe17\quad xor\quad 0x28aed2a6 = 0x88542cb1$
如下就是密钥编排的完整的算法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
Sbox = (
    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, 0x16,
)

Rcon = (0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36)


def text2matrix(text):
    matrix = []
    for i in range(16):
        byte = (text >> (8 * (15 - i))) & 0xFF
        if i % 4 == 0:
            matrix.append([byte])
        else:
            matrix[i // 4].append(byte)
    return matrix


def shiftRound(array, num):
    '''

    :param array: 需要循环左移的数组
    :param num: 循环左移的位数
    :return: 使用Python切片,返回循环左移num个单位的array
    '''
    return array[num:] + array[:num]


def g(array, index):
    '''
    g 函数
    :param array: 待处理的四字节数组
    :index:从1-10,每次使用Rcon中不同的数
    '''
    # 首先循环左移1位
    array = shiftRound(array, 1)
    # 字节替换
    array = [Sbox[i] for i in array]
    # 首字节和rcon中对应元素异或
    array = [(Rcon[index] ^ array[0])] + array[1:]
    return array


def xorTwoArray(array1, array2):
    '''
    返回两个数组逐元素异或的新数组
    :param array1: 一个array
    :param array2: 另一个array
    :return:
    '''
    assert len(array1) == len(array2)
    return [array1[i] ^ array2[i] for i in range(len(array1))]


def showRoundKeys(kList):
    for i in range(len(kList)):
        print("K%02d:" %i +"".join("%02x" % k for k in kList[i]))


def keyExpand(key):
    master_key = text2matrix(key)
    round_keys = [[0] * 4 for i in range(44)]
    # 规则一(图中红色部分)
    for i in range(4):
        round_keys[i] = master_key[i]
    for i in range(4, 4 * 11):
        # 规则二(图中红色部分)
        if i % 4 == 0:
            round_keys[i] = xorTwoArray(g(round_keys[i - 1], i // 4), round_keys[i - 4])
        # 规则三(图中橙色部分)
        else:
            round_keys[i] = xorTwoArray(round_keys[i - 1], round_keys[i - 4])
    # 将轮密钥从44*4转成11*16,方便后面在明文的运算里使用
    kList = [[] for i in range(11)]
    for i in range(len(round_keys)):
        kList[i//4] += round_keys[i]

    showRoundKeys(kList)
    return kList


input_bytes = 0x00112233445566778899aabbccddeeff
key = 0x2b7e151628aed2a6abf7158809cf4f3c
kList = keyExpand(key)

运行结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
K00:2b7e151628aed2a6abf7158809cf4f3c
K01:a0fafe1788542cb123a339392a6c7605
K02:f2c295f27a96b9435935807a7359f67f
K03:3d80477d4716fe3e1e237e446d7a883b
K04:ef44a541a8525b7fb671253bdb0bad00
K05:d4d1c6f87c839d87caf2b8bc11f915bc
K06:6d88a37a110b3efddbf98641ca0093fd
K07:4e54f70e5f5fc9f384a64fb24ea6dc4f
K08:ead27321b58dbad2312bf5607f8d292f
K09:ac7766f319fadc2128d12941575c006e
K10:d014f9a8c9ee2589e13f0cc8b6630ca6

明文处理

密钥编排部分说完了,接下来该看一下明文的处理了即上面流程图中左边的部分
首先,需要调整明文的格式,在 AES 中,数据以 $state$ 的形式计算,中间存储与传输,中文名即状态
从明文转到 state 形式很简单,以我们上面的那个明文为例: 00112233445566778899aabbccddeeff,从上到下,从左至右

接着,轮密钥加,为什么叫轮密钥加呢?因为在算法中加就是异或
这个是第一次轮密钥加的步骤,所以也叫做初始轮密钥加,也就是将明文的 state 状态与初始轮密钥进行逐字节异或
接下来就是 10 轮主运算,看如下的伪代码,可以清楚的看到一轮运算中有什么,以及第十轮和前九轮有什么区别

初始的明文转 state 和最后的 state 转明文自不用说,然后是初始轮密钥,使用 $K_0$
前 9 轮运算中,包含四个步骤:字节替换,循环左移,列混淆,轮密钥加
第 10 轮种,包含三个步骤:字节替换,循环左移,轮密钥加,相比较于前 9 轮,少了一个列混淆,其余的都是相同的
而字节替换步骤,和密钥编排中的 S 盒替换完全一致
循环左移,和密钥编排中的循环左移类似,但不完全相同。在密钥编排中,函数中也需要循环左移,但其中待处理的数据仅有一行,而明文编排中有 4 行,其循环左移规则如下:
第一行不循环左移,第二行循环左移 1 字节,第三行循环左移 2 字节,第四行循环左移 3 字节

列混淆有点麻烦啊,就不写了吧
所有的代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
Sbox = (
    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, 0x16,
)

Rcon = (0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36)


def text2matrix(text):
    matrix = []
    for i in range(16):
        byte = (text >> (8 * (15 - i))) & 0xFF
        if i % 4 == 0:
            matrix.append([byte])
        else:
            matrix[i // 4].append(byte)
    return matrix


def shiftRound(array, num):
    '''

    :param array: 需要循环左移的数组
    :param num: 循环左移的位数
    :return: 使用Python切片,返回循环左移num个单位的array
    '''
    return array[num:] + array[:num]


def g(array, index):
    '''
    g 函数
    :param array: 待处理的四字节数组
    :index:从1-10,每次使用Rcon中不同的数
    '''
    # 首先循环左移1位
    array = shiftRound(array, 1)
    # 字节替换
    array = [Sbox[i] for i in array]
    # 首字节和rcon中对应元素异或
    array = [(Rcon[index] ^ array[0])] + array[1:]
    return array


def xorTwoArray(array1, array2):
    '''
    返回两个数组逐元素异或的新数组
    :param array1: 一个array
    :param array2: 另一个array
    :return:
    '''
    assert len(array1) == len(array2)
    return [array1[i] ^ array2[i] for i in range(len(array1))]


def showRoundKeys(round_keys):
    # 将轮密钥从44*4转成11*16
    kList = [[] for i in range(11)]
    for i in range(len(round_keys)):
        kList[i // 4] += round_keys[i]
    for i in range(len(kList)):
        print("K%02d:" % i + "".join("%02x" % k for k in kList[i]))


def keyExpand(key):
    master_key = text2matrix(key)
    round_keys = [[0] * 4 for i in range(44)]
    # 规则一(图中红色部分)
    for i in range(4):
        round_keys[i] = master_key[i]
    for i in range(4, 4 * 11):
        # 规则二(图中红色部分)
        if i % 4 == 0:
            round_keys[i] = xorTwoArray(g(round_keys[i - 1], i // 4), round_keys[i - 4])
        # 规则三(图中橙色部分)
        else:
            round_keys[i] = xorTwoArray(round_keys[i - 1], round_keys[i - 4])
    showRoundKeys(round_keys)
    return round_keys


def AddRoundKeys(state, roundKey):
    result = [[] for i in range(4)]
    for i in range(4):
        result[i] = xorTwoArray(state[i], roundKey[i])

    return result


def SubBytes(state):
    result = [[] for i in range(4)]
    for i in range(4):
        result[i] = [Sbox[i] for i in state[i]]
    return result


def ShiftRows(s):
    s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
    s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
    s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
    return s


def mul_by_02(num):
    if num < 0x80:
        res = (num << 1)
    else:
        res = (num << 1) ^ 0x1b
    return res % 0x100


def mul_by_03(num):
    return mul_by_02(num) ^ num


def MixColumns(state):
    for i in range(4):
        s0 = mul_by_02(state[i][0]) ^ mul_by_03(state[i][1]) ^ state[i][2] ^ state[i][3]
        s1 = state[i][0] ^ mul_by_02(state[i][1]) ^ mul_by_03(state[i][2]) ^ state[i][3]
        s2 = state[i][0] ^ state[i][1] ^ mul_by_02(state[i][2]) ^ mul_by_03(state[i][3])
        s3 = mul_by_03(state[i][0]) ^ state[i][1] ^ state[i][2] ^ mul_by_02(state[i][3])
        state[i][0] = s0
        state[i][1] = s1
        state[i][2] = s2
        state[i][3] = s3

    return state


def state2Text(state):
    text = sum(state, [])
    return "".join("%02x" % k for k in text)


def encrypt(input_bytes, kList):
    '''

    :param input_bytes: 输入的明文
    :param kList: K0-K10
    :return:
    '''
    plainState = text2matrix(input_bytes)
    # 初始轮密钥加
    state = AddRoundKeys(plainState, kList[0:4])
    for i in range(1, 10):
        state = SubBytes(state)
        state = ShiftRows(state)
        if i ==9:
            state[0][0] = 0
        state = MixColumns(state)
        state = AddRoundKeys(state, kList[4 * i:4 * (i + 1)])

    state = SubBytes(state)
    state = ShiftRows(state)
    state = AddRoundKeys(state, kList[40:44])
    return state


input_bytes = 0x00112233445566778899aabbccddeeff
key = 0x2b7e151628aed2a6abf7158809cf4f3c
kList = keyExpand(key)
cipherState = encrypt(input_bytes, kList)
cipher = state2Text(cipherState)
print(cipher)

AES CBC 模式的计算流程

当 AES 加密是 CBC 模式的时候,明文首先会与 IV 异或,然后将结果进行块加密,输出明文,同时本次输出密文作为下一个块加密的 IV
IV 同样也需要转为 state 然后与明文的 state 进行异或
例如 IV 是 00112233445566778899aabbccddeeff

仅需要修改以下代码就可以实现 AES 的 CBC 加密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def InputXorIv(state, ivState):
    result = [[] for i in range(4)]
    for i in range(4):
        result[i] = xorTwoArray(state[i], ivState[i])
    return result

def encrypt(input_bytes, iv, kList):
    '''
    :param input_bytes: 输入的明文
    :param kList: K0-K10
    :return:
    '''
    ivState = text2matrix(iv)
    plainState = text2matrix(input_bytes)
    # 明文与iv异或
    plainState = InputXorIv(plainState, ivState)
    # 初始轮密钥加 K0
    state = AddRoundKeys(plainState, kList[0:4])
    for i in range(1, 10):
        # 字节替换
        state = SubBytes(state)
        # 循环左移
        state = ShiftRows(state)
        # 列混淆
        state = MixColumns(state)
        # 轮密钥加 K1-K9
        state = AddRoundKeys(state, kList[4 * i:4 * (i + 1)])
    # 字节替换
    state = SubBytes(state)
    # 循环左移
    state = ShiftRows(state)
    # 轮密钥加 K10
    state = AddRoundKeys(state, kList[40:44])
    return state

input_bytes = 0x00112233445566778899aabbccddeeff
key = 0x2b7e151628aed2a6abf7158809cf4f3c
iv = 0x2b7e151628aed2a6abf7158809cf4f3c
kList = keyExpand(key)
cipherState = encrypt(input_bytes, iv, kList)
cipher = state2Text(cipherState)
print(cipher)

参考文章

龙哥星球文章
https://www.yuque.com/nanren-w8l2z/xgu63m/uoff9ovsmhqki9wh?singleDoc#cjBWY