白乐天

道阻且长,行则将至。

详解AES

AES简介

AES的全称是Advanced Encryption Standard,意思是高级加密标准,是一种对称密钥加密算法,它的出现主要是为了取代DES加密算法的。AES是一种块加密算法,它将数据分成固定大小的块(通常是128位),然后进行加密。

块大小:AES加密算法使用128位(16字节)的块来处理数据。无论输入数据的大小是多少,AES都会将其分割成多个128位块进行加密。

秘钥长度:AES支持三种不同长度的密钥:

  • AES-128:密钥长度为128位(16字节)
  • AES-192:密钥长度为192位(24字节)
  • AES-256:密钥长度为256位(32字节)

加密流程

AES的加密流程可以分为3个步骤。

初始轮(Initial Round)

轮密钥加(AddRoundKey)

在初始轮,输入的明文块(128位)与扩展出的第一轮密钥(轮密钥)进行按位异或(XOR)操作。

中间轮(Rounds)

中间轮在初始轮之后,会重复进行多次(取决于密钥长度,AES-128通常为9轮,AES-192为11轮,AES-256为13轮)。

字节代换(SubBytes)

字节代换使用一个固定的S-Box(代换盒),将数据块中的每个字节都进行代换。S-Box是一个非线性的代换表,可以增加算法的复杂性和安全性。

对于状态矩阵中的每个字节,AES通过查找S-Box表中的对应值来替换。例如,如果某个字节的值是0x32,那么该字节将被替换为S-Box中0x32位置对应的值。

行移位(ShiftRows)

对状态矩阵中的每一行进行循环左移。具体来说:

  • 第一行不变。
  • 第二行左移1个字节。
  • 第三行左移2个字节。
  • 第四行左移3个字节。

列混合(MixColumns)

每一列的四个字节会与一个固定矩阵相乘,生成新的四个字节。

轮密钥加(AddRoundKey)

将当前状态矩阵与当前轮的轮密钥进行按位异或(XOR)操作。轮密钥是在密钥扩展阶段从原始密钥中生成的,每一轮都有不同的轮密钥。

最终轮(Final Round)

最终轮与中间轮类似,但没有列混合步骤。

最终轮执行完后,生成的内容就是AES加密后的密文。

密钥扩展

密钥扩展(Key Expansion)是AES加密过程中的一个关键步骤,它将原始的密钥扩展成多个轮密钥(Round Keys),这些轮密钥将在加密过程中使用。密钥扩展的目的是确保在每一轮加密中,使用的密钥都是不同的,以增强算法的安全性。

轮秘钥的生成

对于AES-128(密钥长度为128位,即16字节),AES需要生成10轮的轮密钥(每轮一个轮密钥)。对于AES-192(192位)和AES-256(256位),它们分别需要12轮和14轮的轮密钥。

  • AES-128:原始密钥长度为16字节,扩展出11个轮密钥(包括初始轮)。
  • AES-192:原始密钥长度为24字节,扩展出13个轮密钥。
  • AES-256:原始密钥长度为32字节,扩展出15个轮密钥。

密钥扩展的步骤

初始化

将原始密钥按字节划分为多个字。每个字是4字节(32位)。假设原始密钥长度为128位(16字节),则将其分为4个字:

1
W0, W1, W2, W3

扩展轮秘钥

  • 初始密钥:16字节(4字),记为 W[0]W[3]
  • 扩展密钥:从W[4]扩展到W[44],生成44个字(176字节),用于11轮加密。

生成规则

对于W[i]

  • 如果i不是4的倍数,那么第i列为

    1
    W[i] = W[i-4] ⊕ W[i-1]
  • 如果i是4的倍数,那么第i列为

    1
    W[i] = W[i-4] ⊕ T(W[i-1])

    函数T包含三部分内容

    • 循环左移(RotWord):将输入字的4字节左移1位,如 [a,b,c,d] → [b,c,d,a]
    • S盒替换(SubWord):每个字节通过AES的S盒进行非线性替换。
    • 异或轮常数(Rcon):与轮常数 Rcon[j] = [0x01, 0x02, 0x04, ..., 0x80]异或。

    1
    W4 = W0 ⊕ SubWord(RotWord(W3)) ⊕ Rcon[1]

Python实现AES

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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# encoding=utf-8

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, 0x16]
]

# 计算逆 S_BOX
INV_S_BOX = [[0] * 16 for _ in range(16)]
for i in range(16):
for j in range(16):
s = S_BOX[i][j]
INV_S_BOX[s >> 4][s & 0xF] = (i << 4) | j

RCON = [
[0x01, 0x00, 0x00, 0x00],
[0x02, 0x00, 0x00, 0x00],
[0x04, 0x00, 0x00, 0x00],
[0x08, 0x00, 0x00, 0x00],
[0x10, 0x00, 0x00, 0x00],
[0x20, 0x00, 0x00, 0x00],
[0x40, 0x00, 0x00, 0x00],
[0x80, 0x00, 0x00, 0x00],
[0x1B, 0x00, 0x00, 0x00],
[0x36, 0x00, 0x00, 0x00]
]


# ----------------- 工具函数 -----------------
# PKCS7 填充:如果数据正好为整数个块,则额外添加一个完整块
def pkcs7_pad(data: list, block_size=16):
padding_length = block_size - (len(data) % block_size)
padding = [padding_length] * padding_length
return data + padding


# PKCS7 去填充
def pkcs7_unpad(data: list, block_size=16):
if not data or len(data) % block_size != 0:
raise ValueError("数据长度错误,不能去填充")
pad_len = data[-1]
if pad_len < 1 or pad_len > block_size:
raise ValueError("无效的填充")
if data[-pad_len:] != [pad_len] * pad_len:
raise ValueError("填充不正确")
return data[:-pad_len]


# 列表异或
def xor_list(a, b):
return [x ^ y for x, y in zip(a, b)]


# ----------------- AES 密钥扩展 -----------------
def sub_word(byte):
return S_BOX[byte >> 4][byte & 0xF]


def key_expansion(key, key_size=128):
key = list(key)
nk = {128: 4, 192: 6, 256: 8}[key_size] # 初始密钥的字数
nr = {128: 10, 192: 12, 256: 14}[key_size] # 轮数
words = [key[i * 4:(i + 1) * 4] for i in range(nk)]
for i in range(nk, 4 * (nr + 1)):
temp = words[i - 1].copy()
if i % nk == 0:
temp = temp[1:] + temp[:1]
temp = [sub_word(b) for b in temp]
temp = xor_list(temp, RCON[i // nk - 1])
elif key_size == 256 and i % nk == 0x4:
temp = [sub_word(b) for b in temp]
words.append(xor_list(words[i - nk], temp))
round_keys = []
for i in range(0, len(words), 4):
round_key = []
for j in range(4):
round_key.extend(words[i + j])
round_keys.append(round_key[:16])
print("round_keys:",round_keys)
return round_keys


# ----------------- 状态与字节转换 -----------------
# bytes 到 state(4×4 矩阵,列优先)
def bytes_to_state(block):
state = []
for c in range(4):
col = []
for r in range(4):
col.append(block[r + 4 * c])
state.append(col)
return state


# state 到 bytes
def state_to_bytes(state):
block = []
for c in range(4):
for r in range(4):
block.append(state[c][r])
return block


# ----------------- 基本变换 -----------------
def add_round_key(state, round_key_state):
for c in range(4):
for r in range(4):
state[c][r] ^= round_key_state[c][r]


def sub_bytes(state):
for c in range(4):
for r in range(4):
state[c][r] = S_BOX[state[c][r] >> 4][state[c][r] & 0xF]


def inv_sub_bytes(state):
for c in range(4):
for r in range(4):
byte = state[c][r]
state[c][r] = INV_S_BOX[byte >> 4][byte & 0xF]


def shift_rows(state):
# 第一行不变,从第二行开始左循环移位
for r in range(1, 4):
row = [state[c][r] for c in range(4)]
row = row[r:] + row[:r]
for c in range(4):
state[c][r] = row[c]


def inv_shift_rows(state):
for r in range(1, 4):
row = [state[c][r] for c in range(4)]
row = row[-r:] + row[:-r]
for c in range(4):
state[c][r] = row[c]


# 定义 xtime 与 gmul 用于列混淆
def xtime(x):
return ((x << 1) ^ 0x1b) & 0xFF if (x & 0x80) else (x << 1)


def gmul(a, b):
p = 0
for _ in range(8):
if b & 1:
p ^= a
a = xtime(a)
b >>= 1
return p


def mix_columns(state):
for c in range(4):
col = state[c]
t0 = gmul(0x2, col[0]) ^ gmul(0x3, col[1]) ^ col[2] ^ col[3]
t1 = col[0] ^ gmul(0x2, col[1]) ^ gmul(0x3, col[2]) ^ col[3]
t2 = col[0] ^ col[1] ^ gmul(0x2, col[2]) ^ gmul(0x3, col[3])
t3 = gmul(0x3, col[0]) ^ col[1] ^ col[2] ^ gmul(0x2, col[3])
state[c] = [t0, t1, t2, t3]


def inv_mix_columns(state):
for c in range(4):
col = state[c]
t0 = gmul(0x0e, col[0]) ^ gmul(0x0b, col[1]) ^ gmul(0x0d, col[2]) ^ gmul(0x09, col[3])
t1 = gmul(0x09, col[0]) ^ gmul(0x0e, col[1]) ^ gmul(0x0b, col[2]) ^ gmul(0x0d, col[3])
t2 = gmul(0x0d, col[0]) ^ gmul(0x09, col[1]) ^ gmul(0x0e, col[2]) ^ gmul(0x0b, col[3])
t3 = gmul(0x0b, col[0]) ^ gmul(0x0d, col[1]) ^ gmul(0x09, col[2]) ^ gmul(0x0e, col[3])
state[c] = [t0, t1, t2, t3]


# ----------------- AES 加密 -----------------
def aes_encrypt(plaintext, key, iv=None, key_size=128):
plaintext = list(plaintext)
key_length_map = {128: 16, 192: 24, 256: 32}
if key_size not in key_length_map:
raise ValueError("key_size 必须为 128/192/256")
if len(key) != key_length_map[key_size]:
raise ValueError(f"密钥长度错误:期望 {key_length_map[key_size]} 字节,实际 {len(key)} 字节")
if iv is not None and len(iv) != 16:
raise ValueError("IV 必须为16字节")
block_size = 16
padded = pkcs7_pad(plaintext, block_size)
num_blocks = len(padded) // block_size
round_keys = key_expansion(key, key_size)
round_key_states = [bytes_to_state(rk) for rk in round_keys]
nr = {128: 10, 192: 12, 256: 14}[key_size]
result = []
# 在 CBC 模式下,需要更新 iv;若 iv 存在则使用其值
for b in range(num_blocks):
block = padded[b * block_size:(b + 1) * block_size]
if iv:
block = xor_list(block, iv)
state = bytes_to_state(block)
add_round_key(state, round_key_states[0])
for rnd in range(1, nr):
sub_bytes(state)
shift_rows(state)
mix_columns(state)
add_round_key(state, round_key_states[rnd])
# 最后一轮(不做列混淆)
sub_bytes(state)
shift_rows(state)
add_round_key(state, round_key_states[nr])
cipher_block = state_to_bytes(state)
if iv:
iv = cipher_block # CBC 模式下,更新 IV 为上个密文块
result.extend(cipher_block)
return bytes(result)


# ----------------- AES 解密 -----------------
def aes_decrypt(ciphertext, key, iv=None, key_size=128):
ciphertext = list(ciphertext)
key_length_map = {128: 16, 192: 24, 256: 32}
if key_size not in key_length_map:
raise ValueError("key_size 必须为 128/192/256")
if len(key) != key_length_map[key_size]:
raise ValueError(f"密钥长度错误:期望 {key_length_map[key_size]} 字节,实际 {len(key)} 字节")
if iv is not None and len(iv) != 16:
raise ValueError("IV 必须为16字节")
block_size = 16
if len(ciphertext) % block_size != 0:
raise ValueError("密文长度必须为块大小的整数倍")
num_blocks = len(ciphertext) // block_size
round_keys = key_expansion(key, key_size)
round_key_states = [bytes_to_state(rk) for rk in round_keys]
nr = {128: 10, 192: 12, 256: 14}[key_size]
result = []
# 在 CBC 模式下,需保存初始 iv 用于异或恢复
prev_cipher = iv if iv else None
for b in range(num_blocks):
block = ciphertext[b * block_size:(b + 1) * block_size]
state = bytes_to_state(block)
# 初始逆变换:最后一轮的逆操作
add_round_key(state, round_key_states[nr])
inv_shift_rows(state)
inv_sub_bytes(state)
# 中间轮次(从 nr-1 到 1)
for rnd in range(nr - 1, 0, -1):
add_round_key(state, round_key_states[rnd])
inv_mix_columns(state)
inv_shift_rows(state)
inv_sub_bytes(state)
# 最后一轮:加上第一轮密钥
add_round_key(state, round_key_states[0])
plain_block = state_to_bytes(state)
if iv:
plain_block = xor_list(plain_block, prev_cipher)
prev_cipher = block # 更新 prev_cipher 为当前密文块
result.extend(plain_block)
# 去除 PKCS7 填充
return bytes(pkcs7_unpad(result, block_size))


# ----------------- 示例函数 -----------------
data = b"Bileton"
key = b"0123456789abcdef"
print(aes_encrypt(data,key).hex())
# cef043b604dfa84449dd3a1e5bf117e6

print(aes_decrypt(bytes.fromhex("cef043b604dfa84449dd3a1e5bf117e6"),key).decode())
# Bileton