Featured image of post AES 学习笔记

AES 学习笔记

1. 简介 (Introduction)

AES (Advanced Encryption Standard),即高级加密标准,是目前全球最广泛使用的对称加密算法。它由比利时密码学家 Joan Daemen 和 Vincent Rijmen 设计,原名 Rijndael。2001年被 NIST 正式采纳为标准 (FIPS 197),用以取代老旧的 DES。

1.1 关键参数

  • 分组长度 (Block Size): 固定为 128位 (16字节)
  • 密钥长度 (Key Length): 支持 128、192、256 位。本笔记主要讨论 AES-128
  • 轮数 (Rounds):
    • AES-128: 10 轮
    • AES-192: 12 轮
    • AES-256: 14 轮

2. 数学基础 (Mathematical Background)

AES 的运算不是在普通的整数域上进行的,而是在 有限域 (Finite Field),特别是 伽罗瓦域 $GF(2^8)$ 上进行的。这保证了运算结果始终在一个字节(8位)范围内,且具有良好的非线性特性。

2.1 字节的表示

一个字节 $b_7 b_6 b_5 b_4 b_3 b_2 b_1 b_0$ 可以看作是系数为 ${0, 1}$ 的多项式:

$$ b(x) = b_7x^7 + b_6x^6 + b_5x^5 + b_4x^4 + b_3x^3 + b_2x^2 + b_1x + b_0 $$

例如:十六进制 0x57 (二进制 01010111) 对应多项式 $x^6 + x^4 + x^2 + x + 1$。

2.2 域上的加法 (Addition)

在 $GF(2^8)$ 中,加法就是多项式系数模 2 相加,等价于 异或 (XOR) 运算。

$$ a + b \iff a \oplus b $$

例如:0x57 + 0x83 = 01010111 ^ 10000011 = 11010100 = 0xD4

2.3 域上的乘法 (Multiplication)

乘法是多项式相乘后,模一个不可约多项式 (Irreducible Polynomial) $m(x)$。AES 规定的不可约多项式为:

$$ m(x) = x^8 + x^4 + x^3 + x + 1 \quad (\text{Hex: } 0x11B) $$

xtime 函数: 这是 AES 乘法的核心。xtime(a) 相当于 $a$ 乘以 $x$(即左移一位)。

  • 如果 $a$ 的最高位 $b_7 = 0$,则 xtime(a) = a << 1
  • 如果 $a$ 的最高位 $b_7 = 1$,则 xtime(a) = (a << 1) ^ 0x1B (模 $m(x)$ 的结果)。
  • 任意乘法 $a \times b$ 可以通过重复 xtime 和加法(XOR)组合实现。

3. 算法内部结构 (Internal Structure)

AES 处理的数据单位是 状态矩阵 (State Matrix)。16 字节的输入被排列成 $4 \times 4$ 的矩阵(按列优先填充)。

$$ \text{State} = \begin{bmatrix} s_{0,0} & s_{0,1} & s_{0,2} & s_{0,3} \\ s_{1,0} & s_{1,1} & s_{1,2} & s_{1,3} \\ s_{2,0} & s_{2,1} & s_{2,2} & s_{2,3} \\ s_{3,0} & s_{3,1} & s_{3,2} & s_{3,3} \end{bmatrix} $$

3.1 整体流程 (AES-128)

  1. 密钥扩展 (Key Expansion): 将初始密钥扩展为 11 组轮密钥 ($K_0, K_1, …, K_{10}$)。
  2. 初始轮 (Initial Round):
    • AddRoundKey (与 $K_0$ 异或)
  3. 常规轮 (Rounds 1 to 9):
    • SubBytes (字节代换)
    • ShiftRows (行移位)
    • MixColumns (列混合)
    • AddRoundKey (与 $K_i$ 异或)
  4. 最终轮 (Final Round 10):
    • SubBytes
    • ShiftRows
    • AddRoundKey (与 $K_{10}$ 异或)
    • (注意:最终轮没有 MixColumns)

4. 详细步骤解析 (Detailed Transformations)

4.1 字节代换 (SubBytes)

这是 AES 中唯一的非线性变换,提供了混淆性 (Confusion)。

  • 原理: 每个字节 $b$ 被替换为 S-Box 中的对应值。
  • S-Box 构造:
    1. 求 $b$ 在 $GF(2^8)$ 中的乘法逆元 $b^{-1}$ (0 的逆元设为 0)。
    2. 对 $b^{-1}$ 进行仿射变换 (Affine Transformation)。
  • 查表法: 实际实现通常直接查预计算好的 $16 \times 16$ S-Box 表。

4.2 行移位 (ShiftRows)

对状态矩阵的每一行进行循环左移,提供了扩散性 (Diffusion)。

  • 第 0 行:不移动。
  • 第 1 行:循环左移 1 字节。
  • 第 2 行:循环左移 2 字节。
  • 第 3 行:循环左移 3 字节。
$$ \begin{bmatrix} s_{0,0} & s_{0,1} & s_{0,2} & s_{0,3} \\ s_{1,0} & s_{1,1} & s_{1,2} & s_{1,3} \\ s_{2,0} & s_{2,1} & s_{2,2} & s_{2,3} \\ s_{3,0} & s_{3,1} & s_{3,2} & s_{3,3} \end{bmatrix} \xrightarrow{\text{ShiftRows}} \begin{bmatrix} s_{0,0} & s_{0,1} & s_{0,2} & s_{0,3} \\ s_{1,1} & s_{1,2} & s_{1,3} & s_{1,0} \\ s_{2,2} & s_{2,3} & s_{2,0} & s_{2,1} \\ s_{3,3} & s_{3,0} & s_{3,1} & s_{3,2} \end{bmatrix} $$

4.3 列混合 (MixColumns)

对状态矩阵的每一列进行线性变换,进一步增强扩散性。

  • 原理: 将每一列视为 $GF(2^8)$ 上的多项式,与一个固定的多项式 $c(x)$ 进行模 $x^4 + 1$ 乘法。

    $$ c(x) = 03x^3 + 01x^2 + 01x + 02 $$
  • 矩阵形式:

    $$ \begin{bmatrix} b_0 \\ b_1 \\ b_2 \\ b_3 \end{bmatrix} = \begin{bmatrix} 02 & 03 & 01 & 01 \\ 01 & 02 & 03 & 01 \\ 01 & 01 & 02 & 03 \\ 03 & 01 & 01 & 02 \end{bmatrix} \times \begin{bmatrix} a_0 \\ a_1 \\ a_2 \\ a_3 \end{bmatrix} $$

    这里的乘法是 $GF(2^8)$ 上的乘法,加法是异或。

4.4 轮密钥加 (AddRoundKey)

将状态矩阵与当前的轮密钥进行按位异或 (XOR)。这是将密钥融入密文的关键步骤。

$$ \text{State} = \text{State} \oplus \text{RoundKey}_i $$

5. 密钥扩展 (Key Expansion)

AES-128 需要将 16 字节的初始密钥扩展为 176 字节(44 个字,每个字 4 字节),供 11 轮使用。 令 $W[0 \dots 43]$ 为扩展后的字数组。

  1. 前 4 个字: 直接复制初始密钥。
  2. 后续字 $W[i]$:
    • 如果 $i$ 是 4 的倍数: $$ W[i] = W[i-4] \oplus \text{SubWord}(\text{RotWord}(W[i-1])) \oplus \text{Rcon}[i/4] $$
    • 否则: $$ W[i] = W[i-4] \oplus W[i-1] $$
  • RotWord: 字循环左移 1 字节 ($[a_0, a_1, a_2, a_3] \to [a_1, a_2, a_3, a_0]$)。
  • SubWord: 对字的每个字节应用 S-Box。
  • Rcon (轮常数): 用于破坏对称性。$Rcon[i] = [x^{i-1}, 00, 00, 00]$ (在 $GF(2^8)$ 中)。

6. 解密流程 (Decryption)

解密是加密的逆过程,需要使用逆变换:

  1. InvSubBytes: 使用逆 S-Box。
  2. InvShiftRows: 循环右移。
  3. InvMixColumns: 乘以 $c(x)$ 的逆多项式 $d(x) = 0Bx^3 + 0Dx^2 + 09x + 0E$。
  4. AddRoundKey: 异或本身就是逆运算。

注意: 解密时轮密钥的使用顺序是相反的(从 $K_{10}$ 到 $K_0$)。


7. 源代码

  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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
import sys
from typing import List

# =============================================================================
# AES 常量定义
# =============================================================================

# S-Box (Substitution Box): 用于 SubBytes 步骤的非线性替换表
# 它是基于 GF(2^8) 上的乘法逆元和仿射变换构造的
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
]

# Inverse S-Box: 用于解密时的逆字节代换
INV_SBOX = [
    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
]

# Rcon (Round Constant): 轮常数,用于密钥扩展
# Rcon[i] 代表 x^(i-1) 在 GF(2^8) 中的值
RCON = [
    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36
]

BLOCK_SIZE = 16  # AES 分组大小固定为 128 位 (16 字节)

# =============================================================================
# 辅助函数:填充与数据转换
# =============================================================================

def pkcs7_pad(data: bytes, block_size: int = BLOCK_SIZE) -> bytes:
    """
    PKCS#7 填充:将数据填充到 block_size 的倍数。
    填充的值等于填充的字节数。
    例如:如果缺 3 个字节,就填充 0x03 0x03 0x03。
    """
    pad_len = block_size - (len(data) % block_size)
    if pad_len == 0:
        pad_len = block_size
    return data + bytes([pad_len] * pad_len)


def pkcs7_unpad(data: bytes) -> bytes:
    """
    PKCS#7 去填充:读取最后一个字节确定填充长度,并移除填充。
    """
    if not data:
        raise ValueError("空数据不支持去填充")
    pad_len = data[-1]
    if pad_len < 1 or pad_len > BLOCK_SIZE:
        raise ValueError("填充长度非法")
    if data[-pad_len:] != bytes([pad_len] * pad_len):
        raise ValueError("填充内容非法")
    return data[:-pad_len]


def zero_pad_key_to_16(key: bytes) -> bytes:
    """
    将密钥处理为 16 字节 (128 位)。
    如果不足 16 字节,右侧补 0;如果超过,截断。
    (仅用于演示,生产环境应使用 KDF)
    """
    if len(key) >= 16:
        return key[:16]
    return key + bytes(16 - len(key))


def bytes_to_state(block: bytes) -> List[List[int]]:
    """
    将 16 字节的输入块转换为 4x4 的状态矩阵 (State)。
    AES 规定按【列优先】顺序填充。
    """
    return [list(block[i::4])[:4] for i in range(4)]


def state_to_bytes(state: List[List[int]]) -> bytes:
    """
    将 4x4 状态矩阵转换回 16 字节数据 (列优先读取)。
    """
    out = bytearray(16)
    for r in range(4):
        for c in range(4):
            out[c * 4 + r] = state[r][c]
    return bytes(out)

# =============================================================================
# AES 核心变换函数
# =============================================================================

def sub_bytes(state: List[List[int]]):
    """
    SubBytes (字节代换):
    使用 S-Box 对状态矩阵中的每个字节进行非线性替换。
    提供了算法的混淆性 (Confusion)。
    """
    for r in range(4):
        for c in range(4):
            state[r][c] = SBOX[state[r][c]]


def inv_sub_bytes(state: List[List[int]]):
    """
    InvSubBytes (逆字节代换):
    使用逆 S-Box 还原字节,用于解密。
    """
    for r in range(4):
        for c in range(4):
            state[r][c] = INV_SBOX[state[r][c]]


def shift_rows(state: List[List[int]]):
    """
    ShiftRows (行移位):
    对状态矩阵的每一行进行循环左移。
    第0行不移,第1行左移1位,第2行左移2位,第3行左移3位。
    提供了算法的扩散性 (Diffusion)。
    """
    state[1] = state[1][1:] + state[1][:1]
    state[2] = state[2][2:] + state[2][:2]
    state[3] = state[3][3:] + state[3][:3]


def inv_shift_rows(state: List[List[int]]):
    """
    InvShiftRows (逆行移位):
    对状态矩阵的每一行进行循环右移,用于解密。
    """
    state[1] = state[1][-1:] + state[1][:-1]
    state[2] = state[2][-2:] + state[2][:-2]
    state[3] = state[3][-3:] + state[3][:-3]


# --- GF(2^8) 数学运算辅助函数 ---

def xtime(a: int) -> int:
    """
    xtime 函数:计算 a * x (在 GF(2^8) 域上)。
    相当于左移一位,如果溢出则异或不可约多项式 0x1B。
    """
    return ((a << 1) & 0xFF) ^ (0x1B if (a & 0x80) else 0)


def mul(a: int, b: int) -> int:
    """
    GF(2^8) 上的乘法:计算 a * b。
    通过重复 xtime 和条件异或实现 (类似快速幂/俄罗斯农民乘法)。
    """
    res = 0
    for i in range(8):
        if b & 1:
            res ^= a
        a = xtime(a)
        b >>= 1
    return res


def mix_single_column(col: List[int]) -> List[int]:
    """
    MixColumns 的单列计算。
    将列向量与固定多项式 c(x) = 03x^3 + 01x^2 + 01x + 02 相乘。
    """
    a0, a1, a2, a3 = col
    return [
        mul(a0, 2) ^ mul(a1, 3) ^ a2 ^ a3,
        a0 ^ mul(a1, 2) ^ mul(a2, 3) ^ a3,
        a0 ^ a1 ^ mul(a2, 2) ^ mul(a3, 3),
        mul(a0, 3) ^ a1 ^ a2 ^ mul(a3, 2)
    ]


def mix_columns(state: List[List[int]]):
    """
    MixColumns (列混合):
    对状态矩阵的每一列进行线性变换,进一步增强扩散性。
    """
    for c in range(4):
        col = [state[r][c] for r in range(4)]
        new_col = mix_single_column(col)
        for r in range(4):
            state[r][c] = new_col[r]


def inv_mix_single_column(col: List[int]) -> List[int]:
    """
    InvMixColumns 的单列计算。
    乘以 c(x) 的逆多项式 d(x) = 0Bx^3 + 0Dx^2 + 09x + 0E。
    """
    a0, a1, a2, a3 = col
    return [
        mul(a0, 0x0e) ^ mul(a1, 0x0b) ^ mul(a2, 0x0d) ^ mul(a3, 0x09),
        mul(a0, 0x09) ^ mul(a1, 0x0e) ^ mul(a2, 0x0b) ^ mul(a3, 0x0d),
        mul(a0, 0x0d) ^ mul(a1, 0x09) ^ mul(a2, 0x0e) ^ mul(a3, 0x0b),
        mul(a0, 0x0b) ^ mul(a1, 0x0d) ^ mul(a2, 0x09) ^ mul(a3, 0x0e)
    ]


def inv_mix_columns(state: List[List[int]]):
    """
    InvMixColumns (逆列混合):用于解密。
    """
    for c in range(4):
        col = [state[r][c] for r in range(4)]
        new_col = inv_mix_single_column(col)
        for r in range(4):
            state[r][c] = new_col[r]


def add_round_key(state: List[List[int]], round_key: bytes):
    """
    AddRoundKey (轮密钥加):
    将状态矩阵与当前的轮密钥进行按位异或 (XOR)。
    """
    for r in range(4):
        for c in range(4):
            state[r][c] ^= round_key[c * 4 + r]


# =============================================================================
# 密钥扩展 (Key Expansion)
# =============================================================================

def rot_word(w: List[int]) -> List[int]:
    """字循环左移:[a0, a1, a2, a3] -> [a1, a2, a3, a0]"""
    return w[1:] + w[:1]


def sub_word(w: List[int]) -> List[int]:
    """字代换:对字中的每个字节应用 S-Box"""
    return [SBOX[b] for b in w]


def key_expansion(key: bytes) -> List[bytes]:
    """
    密钥扩展算法 (AES-128):
    将 16 字节初始密钥扩展为 11 组轮密钥 (每组 16 字节)。
    """
    Nk = 4   # 密钥长度 (字数),AES-128 为 4
    Nb = 4   # 分组长度 (字数),固定为 4
    Nr = 10  # 轮数,AES-128 为 10
    
    # w 存储扩展后的所有字 (4 * (Nr + 1) = 44 个字)
    w = [[0, 0, 0, 0] for _ in range(Nb * (Nr + 1))]
    
    # 前 Nk 个字直接取自初始密钥
    for i in range(Nk):
        w[i] = [key[4 * i + 0], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]]
        
    # 生成剩余的字
    for i in range(Nk, Nb * (Nr + 1)):
        temp = w[i - 1][:]
        if i % Nk == 0:
            # 每 Nk 个字进行一次复杂变换:RotWord + SubWord + XOR Rcon
            temp = sub_word(rot_word(temp))
            temp[0] ^= RCON[i // Nk]
        # W[i] = W[i-Nk] XOR temp
        w[i] = [ (w[i - Nk][j] ^ temp[j]) & 0xFF for j in range(4) ]
        
    # 将字数组重新组合成 11 个 16 字节的轮密钥
    round_keys = []
    for r in range(Nr + 1):
        rk = []
        for c in range(Nb):
            rk.extend(w[r * Nb + c])
        round_keys.append(bytes(rk))
    return round_keys


# =============================================================================
# 调试与输出辅助
# =============================================================================

def bytes_hex(b: bytes) -> str:
    return b.hex().upper()


def state_hex(state: List[List[int]]) -> str:
    return state_to_bytes(state).hex().upper()


# =============================================================================
# AES 加密与解密流程 (Block Level)
# =============================================================================

def aes_encrypt_block(block: bytes, round_keys: List[bytes], verbose: bool = False) -> bytes:
    """
    加密单个 16 字节分组。
    流程:AddRoundKey(Initial) -> 9轮(Sub/Shift/Mix/Add) -> 最终轮(Sub/Shift/Add)
    """
    state = bytes_to_state(block)
    if verbose:
        print("\n==================== 开始 AES 加密 (单块) ===================")
        print(f"输入数据 (Hex): {block.hex().upper()}")
        print(f"初始轮密钥 (Hex): {bytes_hex(round_keys[0])}")
        
    # 初始轮:只做 AddRoundKey
    add_round_key(state, round_keys[0])
    
    # 第 1 到 9 轮
    for round_idx in range(1, 10):
        if verbose:
            print(f"\n-- Round {round_idx} --")
        sub_bytes(state)
        if verbose:
            print(f"After SubBytes: {state_hex(state)}")
        shift_rows(state)
        if verbose:
            print(f"After ShiftRows: {state_hex(state)}")
        mix_columns(state)
        if verbose:
            print(f"After MixColumns: {state_hex(state)}")
        add_round_key(state, round_keys[round_idx])
        if verbose:
            print(f"AddRoundKey ({round_idx}): {bytes_hex(round_keys[round_idx])}")
            print(f"State: {state_hex(state)}")
            
    # 最终轮 (第 10 轮):无 MixColumns
    if verbose:
        print("\n-- Final Round (10) --")
    sub_bytes(state)
    if verbose:
        print(f"After SubBytes: {state_hex(state)}")
    shift_rows(state)
    if verbose:
        print(f"After ShiftRows: {state_hex(state)}")
    add_round_key(state, round_keys[10])
    
    if verbose:
        print(f"AddRoundKey (10): {bytes_hex(round_keys[10])}")
        print(f"[加密结果] (Hex): {state_hex(state)}")
        print("==================== 结束 AES 加密 ====================\n")
        
    return state_to_bytes(state)


def aes_decrypt_block(block: bytes, round_keys: List[bytes], verbose: bool = False) -> bytes:
    """
    解密单个 16 字节分组。
    流程是加密的逆过程,注意轮密钥顺序相反。
    """
    state = bytes_to_state(block)
    if verbose:
        print("\n==================== 开始 AES 解密 (单块) ===================")
        print(f"输入数据 (Hex): {block.hex().upper()}")
        print(f"终止轮密钥 (Hex): {bytes_hex(round_keys[10])}")
        
    # 初始逆变换:先异或最后一轮密钥
    add_round_key(state, round_keys[10])
    inv_shift_rows(state)
    inv_sub_bytes(state)
    
    # 第 9 到 1 轮 (逆序)
    for round_idx in range(9, 0, -1):
        if verbose:
            print(f"\n-- Round {round_idx} --")
        add_round_key(state, round_keys[round_idx])
        if verbose:
            print(f"AddRoundKey ({round_idx}): {bytes_hex(round_keys[round_idx])}")
            print(f"State: {state_hex(state)}")
        inv_mix_columns(state)
        if verbose:
            print(f"After InvMixColumns: {state_hex(state)}")
        inv_shift_rows(state)
        if verbose:
            print(f"After InvShiftRows: {state_hex(state)}")
        inv_sub_bytes(state)
        if verbose:
            print(f"After InvSubBytes: {state_hex(state)}")
            
    # 最终逆变换
    add_round_key(state, round_keys[0])
    
    if verbose:
        print(f"AddRoundKey (0): {bytes_hex(round_keys[0])}")
        print(f"[解密结果] (Hex): {state_hex(state)}")
        print("==================== 结束 AES 解密 ====================\n")
        
    return state_to_bytes(state)


# =============================================================================
# ECB 模式封装
# =============================================================================

def aes_encrypt_ecb(plaintext: bytes, key: bytes, verbose: bool = False) -> bytes:
    """
    AES-128 ECB 模式加密。
    1. 处理密钥
    2. 填充明文 (PKCS#7)
    3. 分块加密
    """
    key16 = zero_pad_key_to_16(key)
    round_keys = key_expansion(key16)
    padded = pkcs7_pad(plaintext, BLOCK_SIZE)
    
    if verbose:
        print("开始加密...")
        print(f"原始长度: {len(plaintext)}, 填充后长度: {len(padded)}")
        
    out = bytearray()
    for i in range(0, len(padded), BLOCK_SIZE):
        blk = padded[i:i+BLOCK_SIZE]
        if verbose:
            try:
                print(f"\n>>> 处理第 {i//BLOCK_SIZE + 1} 块: {blk.decode('utf-8', 'ignore')}")
            except Exception:
                print(f"\n>>> 处理第 {i//BLOCK_SIZE + 1} 块 (Hex): {blk.hex().upper()}")
        ct = aes_encrypt_block(blk, round_keys, verbose)
        out.extend(ct)
    return bytes(out)


def aes_decrypt_ecb(ciphertext: bytes, key: bytes, verbose: bool = False) -> bytes:
    """
    AES-128 ECB 模式解密。
    1. 处理密钥
    2. 分块解密
    3. 去填充 (PKCS#7)
    """
    key16 = zero_pad_key_to_16(key)
    round_keys = key_expansion(key16)
    out = bytearray()
    
    if verbose:
        print("开始解密...")
        
    for i in range(0, len(ciphertext), BLOCK_SIZE):
        blk = ciphertext[i:i+BLOCK_SIZE]
        if verbose:
            print(f"\n>>> 处理第 {i//BLOCK_SIZE + 1} 块 (Hex): {blk.hex().upper()}")
        pt = aes_decrypt_block(blk, round_keys, verbose)
        out.extend(pt)
    return pkcs7_unpad(bytes(out))


def main():
    raw_text = "hello fanshanng"
    raw_key = "secret"
    print(f"原始明文: {raw_text}")
    print(f"原始密钥: {raw_key}")
    
    pt = raw_text.encode("utf-8")
    key_bytes = raw_key.encode("utf-8")
    
    # 加密
    ct = aes_encrypt_ecb(pt, key_bytes, verbose=True)
    print(f"\n最终密文 (Hex): {ct.hex().upper()}")
    
    # 解密
    dec = aes_decrypt_ecb(ct, key_bytes, verbose=True)
    dec_str = dec.decode("utf-8")
    
    print("\n最终验证:")
    print(f"  原始明文: {raw_text}")
    print(f"  解密明文: {dec_str}")
    print(f"  匹配成功: {raw_text == dec_str}")


if __name__ == "__main__":
    main()

8 代码详解:

8.1列混合 (Code Deep Dive: MixColumns)

AES 的列混合(MixColumns)是整个算法中数学味最浓、也最难理解的部分。它本质上是在 有限域 $GF(2^8)$ 上进行的 矩阵乘法

1. 最底层:xtime (x 乘法)

这是 AES 数学运算的基石。在有限域 $GF(2^8)$ 中,数字被视为多项式的系数。

  • 加法/减法:就是异或 (XOR)。
  • 乘法:比较复杂,特别是乘以 2(即多项式 $x$)。
1
2
3
4
5
6
7
8
9
def xtime(a: int) -> int:
    """
    xtime 函数:计算 a * 2 (在 GF(2^8) 域上)。
    相当于左移一位,如果溢出则异或不可约多项式 0x1B。
    """
    # (a << 1) & 0xFF : 左移一位,相当于乘以 x。& 0xFF 确保结果在 8 位以内。
    # (a & 0x80)      : 检查最高位(第8位)是否为 1。
    # ^ 0x1B          : 如果最高位是 1,说明溢出了,需要减去(异或)不可约多项式 0x1B (00011011)。
    return ((a << 1) & 0xFF) ^ (0x1B if (a & 0x80) else 0)

通俗解释: 这就好比我们在十进制里规定“最大不能超过 99”。如果你算 $50 \times 2 = 100$,超过了,就得减去一个特定的数(比如 100)让它变回 0。 在 AES 的 8 位世界里,如果左移导致最高位变成了 1(溢出),我们就异或 0x1B 来“折叠”回去。

2. 中间层:mul (通用乘法)

有了 xtime (乘以 2),我们就可以计算乘以任意数了。这里用的是**“俄罗斯农民乘法”**(也叫倍增减半法)的变体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def mul(a: int, b: int) -> int:
    """
    GF(2^8) 上的乘法:计算 a * b。
    """
    res = 0
    for i in range(8):
        # 如果 b 的当前最低位是 1,就把当前的 a 加(异或)到结果里
        if b & 1:
            res ^= a
        
        # a 翻倍 (乘以 x)
        a = xtime(a)
        
        # b 减半 (右移)
        b >>= 1
    return res

举例计算 $A \times 3$

  • $3$ 的二进制是 00000011
  • $A \times 3 = A \times (2 + 1) = (A \times 2) + (A \times 1)$。
  • 代码里:
    • 第一轮:b 末位是 1,res = res ^ A
    • 第二轮:a 变成了 xtime(A) (即 $2A$),b 末位还是 1,res = res ^ 2A
    • 结果就是 $A \oplus 2A$。

3. 应用层:mix_single_column (矩阵变换)

这一步是真正的列混合。AES 规定每一列都要和一个固定的矩阵相乘。 这个固定矩阵是:

$$ \begin{bmatrix} 2 & 3 & 1 & 1 \\ 1 & 2 & 3 & 1 \\ 1 & 1 & 2 & 3 \\ 3 & 1 & 1 & 2 \end{bmatrix} $$

代码完全对应这个矩阵乘法公式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def mix_single_column(col: List[int]) -> List[int]:
    a0, a1, a2, a3 = col  # 输入列的 4 个字节
    
    return [
        # 新的第 0 行 = 2*a0 + 3*a1 + 1*a2 + 1*a3
        mul(a0, 2) ^ mul(a1, 3) ^ a2 ^ a3,
        
        # 新的第 1 行 = 1*a0 + 2*a1 + 3*a2 + 1*a3
        a0 ^ mul(a1, 2) ^ mul(a2, 3) ^ a3,
        
        # 新的第 2 行 = 1*a0 + 1*a1 + 2*a2 + 3*a3
        a0 ^ a1 ^ mul(a2, 2) ^ mul(a3, 3),
        
        # 新的第 3 行 = 3*a0 + 1*a1 + 1*a2 + 2*a3
        mul(a0, 3) ^ a1 ^ a2 ^ mul(a3, 2)
    ]

为什么是 2 和 3?

  • 乘以 1:就是它自己(代码里直接写 a2 而不是 mul(a2, 1))。
  • 乘以 2:调用 xtime 一次。
  • 乘以 3:等于 xtime(a) ^ a。 AES 设计者选择 1, 2, 3 是因为它们计算最快(只需要移位和异或),同时能保证很好的扩散效果(让一个字节的变化影响到这一列的所有 4 个字节)。

8.2 代码详解:密钥扩展 (Code Deep Dive: Key Expansion)

AES 的密钥扩展(Key Expansion)负责把用户输入的短密钥(16字节)“拉长”成足够 11 轮加密使用的长密钥串(176字节)。

1. 辅助变换

密钥扩展处理的基本单位是 “字” (Word),即 4 个字节。

  • rot_word(w) (字循环左移)

    1
    2
    3
    
    def rot_word(w: List[int]) -> List[int]:
        # 输入 [a0, a1, a2, a3] -> 输出 [a1, a2, a3, a0]
        return w[1:] + w[:1]
    

    这很简单,就是把第一个字节移到最后去。

  • sub_word(w) (字代换)

    1
    2
    3
    
    def sub_word(w: List[int]) -> List[int]:
        # 对字里的每个字节,都去查 S-Box 替换一下
        return [SBOX[b] for b in w]
    

    这和加密步骤里的 SubBytes 一样,引入非线性,防止攻击者通过线性关系推导密钥。

2. 核心逻辑:key_expansion

AES-128 需要 44 个字($4 \times 11$ 轮 = 44 字)。

 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
def key_expansion(key: bytes) -> List[bytes]:
    Nk = 4   # 原始密钥包含的字数 (16字节 / 4 = 4)
    Nb = 4   # 每轮需要的字数 (固定为 4)
    Nr = 10  # 轮数
    
    # w 是一个大列表,用来存这 44 个字
    w = [[0, 0, 0, 0] for _ in range(Nb * (Nr + 1))]
    
    # 【第一步】前 4 个字 (w[0]~w[3]) 直接就是原始密钥
    for i in range(Nk):
        w[i] = [key[4 * i + 0], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]]
        
    # 【第二步】生成剩下的 40 个字 (w[4]~w[43])
    for i in range(Nk, Nb * (Nr + 1)):
        temp = w[i - 1][:]  # 先取出前一个字
        
        # 每隔 4 个字 (i % Nk == 0),就要进行一次“复杂变换”
        if i % Nk == 0:
            # 1. 循环左移 (RotWord)
            # 2. S盒代换 (SubWord)
            temp = sub_word(rot_word(temp))
            
            # 3. 异或轮常数 (Rcon)
            # RCON[i // Nk] 获取当前是第几轮的常数
            # 只异或第一个字节 (temp[0]),因为 Rcon 的后三个字节都是 0
            temp[0] ^= RCON[i // Nk]
            
        # 核心公式:W[i] = W[i-Nk] XOR temp
        # 也就是说:当前字 = (4个位置前的字) XOR (经过处理的前一个字)
        w[i] = [ (w[i - Nk][j] ^ temp[j]) & 0xFF for j in range(4) ]

逻辑图解: 想象一个滑动窗口。

  • 大多数时候(普通列):新字 = 4个前的字 XOR 前一个字
  • 每逢 4 的倍数(第一列):新字 = 4个前的字 XOR g(前一个字)
    • 函数 $g$ 就是:先左移,再代换,最后异或轮常数。

3. 轮常数 RCON

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

RCON 是为了消除每一轮密钥生成的对称性。

  • 它只作用于每个字的第一个字节
  • 它的值是 $x^{i-1}$ 在 $GF(2^8)$ 上的幂:
    • 第1轮:$x^0 = 1$ (0x01)
    • 第2轮:$x^1 = 2$ (0x02)
    • 第9轮:$x^8$ (溢出,变成 0x1B)
    • 第10轮:$x^9 = x \cdot x^8 = 2 \cdot 0x1B = 0x36$​

9. 附录:运行结果(详细日志)

以下日志展示了对明文 "hello fanshanng" 使用密钥 "secret" 进行加密的完整内部状态。

  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
原始明文: hello fanshanng
原始密钥: secret

开始加密...
原始长度: 15, 填充后长度: 16

>>> 处理第 1 块: hello fanshanng

==================== 开始 AES 加密 (单块) ===================
输入数据 (Hex): 68656C6C6F2066616E7368616E6E6701
初始轮密钥 (Hex): 73656372657400000000000000000000

-- Round 1 --
After SubBytes: 84B05399D8E9F0792B7629157886430A
After ShiftRows: 84E9290AD87643792B86539978B0F015
After MixColumns: D742B4B2C67AE73340810CB134BEA817
AddRoundKey (1): 11060011747200117472001174720011
State: C64CB4A19208872234F30CA0602CB806

-- Round 2 --
After SubBytes: 0D5A8166C7F66F5672BDC6872891B03E
After ShiftRows: 0DF6C63EC7BDB06672918156685A6F87
After MixColumns: 75262BEBCD333CAAA5FC11340EA7F864
AddRoundKey (2): 53658283271782925365828327178292
State: 2643A968EA24BE38F69993B729B07AF6

-- Round 3 --
After SubBytes: F71AD3458736AE0742EEDCA9A5E7DA42
After ShiftRows: F736DC4287EEDA4542E7D307A51AAEA9
After MixColumns: 31A6A46CA370092C62FE11FC78D11809
AddRoundKey (3): A776CD4F80614FDDD304CD5EF4134FCC
State: 96D06923231146F1B1FADCA28CC257C5

-- Round 4 --
After SubBytes: 9070F92626825AA1C82D863A64255BA6
After ShiftRows: 908286A6262D5B26C825F9A164705A3A
After MixColumns: 86B8F4F846B7D750BC33FCC63850EEF2
AddRoundKey (4): D2F286F05293C92D8197047375844BBF
State: 544A720814241E7D3DA4F8B54DD4A54D

-- Round 5 --
After SubBytes: 20D64030FA3672FF274941D5E34806E3
After ShiftRows: 203641E3FA490630274840FFE3D672D5
After MixColumns: B86CAACA0252EF3A2988F5841B17B52B
AddRoundKey (5): 9D418E6DCFD247404E4543333BC1088C
State: 252D24A7CD80A87A67CDB6B720D6BDA7

-- Round 6 --
After SubBytes: 3FD8365CBDCDC2DA85BD4EA9B7F67A5C
After ShiftRows: 3FCD4E5CBDBD7A5C85F636DAB7D8C2A9
After MixColumns: 20308A7A9B0E10A3FCF26AFB6DE81091
AddRoundKey (6): C571EA8F0AA3ADCF44E6EEFC7F27E670
State: E54160F591ADBD6CB814840712CFF6E1

-- Round 7 --
After SubBytes: D983D0E681957A506CFA5FC5C98A42F8
After ShiftRows: D9955FF881FA42E66C8AD050C9837AC5
After MixColumns: AAF1E151A84ECEF7DD58AD4EA89FEA28
AddRoundKey (7): 49FFBB5D435C169207BAF86E789D1E1E
State: E30E5A0CEB12D865DAE25520D002F436

-- Round 8 --
After SubBytes: 11ABBEFEE9C9614D5798FCB77077BF05
After ShiftRows: 11C9FC05E998BFFE5777BE4D70AB61B7
After MixColumns: 9B82340C3BE60DE0C42D90AAD029DB2F
AddRoundKey (8): 978DC9E1D4D1DF73D36B271DABF63903
State: 0C0FFDEDEF37D2931746B7B77BDFE22C

-- Round 9 --
After SubBytes: FE765455DF9AB5DCF05AA9A9219E9871
After ShiftRows: FE9AA971DF5A9855F09E54DC2176B5A9
After MixColumns: 8A40BEC8868D5112CAF7B962C4A0C6E9
AddRoundKey (9): CE9FB2831A4E6DF0C9254AED62D373EE
State: 44DF0C4B9CC33CE203D2F38FA673B507

-- Final Round (10) --
After SubBytes: 1B9EFEB3DE2EEB987BB50D73248FD5C5
After ShiftRows: 1B2E0DC5DEB5D5B37B8FFE98249EEB73
AddRoundKey (10): 9E109A29845EF7D94D7BBD342FA8CEDA
[加密结果] (Hex): 853E97EC5AEB226A36F443AC0B3625A9
==================== 结束 AES 加密 ====================


最终密文 (Hex): 853E97EC5AEB226A36F443AC0B3625A9
开始解密...

>>> 处理第 1 块 (Hex): 853E97EC5AEB226A36F443AC0B3625A9

==================== 开始 AES 解密 (单块) ===================
输入数据 (Hex): 853E97EC5AEB226A36F443AC0B3625A9
终止轮密钥 (Hex): 9E109A29845EF7D94D7BBD342FA8CEDA

-- Round 9 --
AddRoundKey (9): CE9FB2831A4E6DF0C9254AED62D373EE
State: 8A40BEC8868D5112CAF7B962C4A0C6E9
After InvMixColumns: FE9AA971DF5A9855F09E54DC2176B5A9
After InvShiftRows: FE765455DF9AB5DCF05AA9A9219E9871
After InvSubBytes: 0C0FFDEDEF37D2931746B7B77BDFE22C

-- Round 8 --
AddRoundKey (8): 978DC9E1D4D1DF73D36B271DABF63903
State: 9B82340C3BE60DE0C42D90AAD029DB2F
After InvMixColumns: 11C9FC05E998BFFE5777BE4D70AB61B7
After InvShiftRows: 11ABBEFEE9C9614D5798FCB77077BF05
After InvSubBytes: E30E5A0CEB12D865DAE25520D002F436

-- Round 7 --
AddRoundKey (7): 49FFBB5D435C169207BAF86E789D1E1E
State: AAF1E151A84ECEF7DD58AD4EA89FEA28
After InvMixColumns: D9955FF881FA42E66C8AD050C9837AC5
After InvShiftRows: D983D0E681957A506CFA5FC5C98A42F8
After InvSubBytes: E54160F591ADBD6CB814840712CFF6E1

-- Round 6 --
AddRoundKey (6): C571EA8F0AA3ADCF44E6EEFC7F27E670
State: 20308A7A9B0E10A3FCF26AFB6DE81091
After InvMixColumns: 3FCD4E5CBDBD7A5C85F636DAB7D8C2A9
After InvShiftRows: 3FD8365CBDCDC2DA85BD4EA9B7F67A5C
After InvSubBytes: 252D24A7CD80A87A67CDB6B720D6BDA7

-- Round 5 --
AddRoundKey (5): 9D418E6DCFD247404E4543333BC1088C
State: B86CAACA0252EF3A2988F5841B17B52B
After InvMixColumns: 203641E3FA490630274840FFE3D672D5
After InvShiftRows: 20D64030FA3672FF274941D5E34806E3
After InvSubBytes: 544A720814241E7D3DA4F8B54DD4A54D

-- Round 4 --
AddRoundKey (4): D2F286F05293C92D8197047375844BBF
State: 86B8F4F846B7D750BC33FCC63850EEF2
After InvMixColumns: 908286A6262D5B26C825F9A164705A3A
After InvShiftRows: 9070F92626825AA1C82D863A64255BA6
After InvSubBytes: 96D06923231146F1B1FADCA28CC257C5

-- Round 3 --
AddRoundKey (3): A776CD4F80614FDDD304CD5EF4134FCC
State: 31A6A46CA370092C62FE11FC78D11809
After InvMixColumns: F736DC4287EEDA4542E7D307A51AAEA9
After InvShiftRows: F71AD3458736AE0742EEDCA9A5E7DA42
After InvSubBytes: 2643A968EA24BE38F69993B729B07AF6

-- Round 2 --
AddRoundKey (2): 53658283271782925365828327178292
State: 75262BEBCD333CAAA5FC11340EA7F864
After InvMixColumns: D742B4B2C67AE73340810CB134BEA817
After InvShiftRows: D7BE0C33C642A8B1407AB4173481E7B2
After InvSubBytes: 0D5A8166C7F66F5672BDC6872891B03E

-- Round 1 --
AddRoundKey (1): 11060011747200117472001174720011
State: 1C5C8177B3846F4706CFC6965CE3B02F
After InvMixColumns: AF20457C678F85729F9F76EF9F6333EF
After InvShiftRows: AF637672672033EF9F8F45EF9F9F857C
After InvSubBytes: 1B000F1E0A5466616E7368616E6E6701
AddRoundKey (0): 73656372657400000000000000000000
[解密结果] (Hex): 68656C6C6F2066616E7368616E6E6701
==================== 结束 AES 解密 ====================


最终验证:
  原始明文: hello fanshanng
  解密明文: hello fanshanng
  匹配成功: True
前途似海,来日方长。


<