mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1048 字
3 分钟
AES从原理到cpp实现

AES加密算法从原理到CPP实现#

一、流程与对比#

说这个之前,先说说不同的AES。AES的密钥输入不是固定的,有三种,16字节,24字节,32字节。

下面以 AES-128 为例详细讲解。AES-192/256 的结构相同,只是密钥扩展的 Nk、轮数 Nr 不同:AES-128 为 10 轮,AES-192 为 12 轮,AES-256 为 14 轮。

AES 是分组密码,分组长度固定为 128 bit = 16 字节。 AES-128 使用 128 bit 密钥,加密过程分为:

  1. 密钥扩展:把 16 字节密钥扩展成 11 个轮密钥,每个 16 字节。
  2. 初始轮密钥加:AddRoundKey(K0)。
  3. 第 1~9 轮:
    • SubBytes
    • ShiftRows
    • MixColumns
    • AddRoundKey(Kr)
  4. 第 10 轮,不执行 MixColumns:
    • SubBytes
    • ShiftRows
    • AddRoundKey(K10)
属性值
分组大小128 bit(16 字节)
密钥长度128 / 192 / 256 bit
轮数10 / 12 / 14(对应密钥长度)
结构SP 网络(不是 Feistel)
状态表示4×4 字节矩阵
安全性✅ 至今未被有效攻破

AES 与 DES 的本质区别:

维度DESAES
结构FeistelSP 网络
分组64 bit128 bit
每轮操作只处理右半处理整个状态
核心非线性S 盒(6→4 bit)S 盒(8→8 bit)
数学基础位运算GF(2⁸) 有限域
解密结构相同结构不同(逆操作)

AES 没有 Feistel 的对称性——解密必须实现每一步的逆操作,不能像 DES 那样复用加密结构。

二、原理#

加密

flowchart TD %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef input fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef state fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef keyAdd fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843; classDef roundLoop fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef subBytes fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d; classDef shiftRows fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#166534; classDef mixColumns fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef finalRound fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d; classDef output fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95,rx:10,ry:10; A["明文<br/>128 bit / 16 字节"]:::input B["按列主序放入<br/>4×4 状态矩阵"]:::state C["AddRoundKey(K0)"]:::keyAdd D["第 1 ~ 9 轮"]:::roundLoop E["SubBytes<br/>字节代换"]:::subBytes F["ShiftRows<br/>行移位"]:::shiftRows G["MixColumns<br/>列混淆"]:::mixColumns H["AddRoundKey(Kr)<br/>Kr = K1 ~ K9"]:::keyAdd I["第 10 轮<br/>(无 MixColumns)"]:::finalRound J["SubBytes"]:::subBytes K["ShiftRows"]:::shiftRows L["AddRoundKey(K10)"]:::keyAdd M["输出 128 bit 密文"]:::output A --> B B --> C C --> D D --> E E --> F F --> G G --> H H -->|"r = r + 1,直到第 9 轮"| D D -->|"1~9 轮结束"| I I --> J J --> K K --> L L --> M %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px; linkStyle 8 stroke:#d97706,stroke-width:2.5px,stroke-dasharray:5 5;

解密

flowchart TD %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef input fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95,rx:10,ry:10; classDef state fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef keyAdd fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843; classDef roundLoop fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef invShiftRows fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#166534; classDef invSubBytes fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d; classDef invMixColumns fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef finalRound fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d; classDef output fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; A["密文<br/>128 bit / 16 字节"]:::input B["按列主序放入<br/>4×4 状态矩阵"]:::state C["AddRoundKey(K10)"]:::keyAdd D["第 1 ~ 9 轮<br/>(逆序)"]:::roundLoop E["AddRoundKey(Kr)<br/>Kr = K9 ~ K1"]:::keyAdd F["InvMixColumns<br/>逆列混淆"]:::invMixColumns G["InvShiftRows<br/>逆行移位"]:::invShiftRows H["InvSubBytes<br/>逆字节代换"]:::invSubBytes I["第 10 轮<br/>(无 InvMixColumns)"]:::finalRound J["AddRoundKey(K0)"]:::keyAdd K["InvShiftRows"]:::invShiftRows L["InvSubBytes"]:::invSubBytes M["输出 128 bit 明文"]:::output A --> B B --> C C --> D D --> E E --> F F --> G G --> H H -->|"r = r - 1,直到第 9 轮"| D D -->|"1~9 轮结束"| I I --> J J --> K K --> L L --> M %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px; linkStyle 8 stroke:#d97706,stroke-width:2.5px,stroke-dasharray:5 5;

明文的加密#

由于是分组加密,所以输入的明文数据一定是16字节的倍数,不足需要填充。

我们先来看看明文的处理过程。

1.状态矩阵#

AES 把 16 字节明文排成 4×4 矩阵,按列优先填充:

明文 = [b0, b1, b2, ..., b15]
状态矩阵:
col0 col1 col2 col3
row0 b0 b4 b8 b12
row1 b1 b5 b9 b13
row2 b2 b6 b10 b14
row3 b3 b7 b11 b15

举例:明文 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

状态矩阵:
00 44 88 CC
11 55 99 DD
22 66 AA EE
33 77 BB FF

记住:AES是列优先

2.四个核心操作#

SubBytes(字节替换)#

对状态矩阵每个字节独立查一个固定的 S 盒(256 字节的表)。

flowchart LR A["状态矩阵<br/>16 字节"] --> B["每个字节<br/>查 S 盒"] B --> C["新状态矩阵"]

S 盒构造分两步:

  1. 在 GF(2⁸) 上求乘法逆元(0 映射到 0)
  2. 做一次仿射变换(affine transformation)

S 盒是 AES 唯一的非线性部件,是安全性的核心,好复杂,想深入的大佬可以研究一下,我不会就不讲了。

下面是S盒的样子,一共256字节。

1

ShiftRows(行移位)#

对状态矩阵的每一行循环左移,移位量按行递增:

第 0 行:不移
第 1 行:左移 1 字节
第 2 行:左移 2 字节
第 3 行:左移 3 字节
移位前:
a0 a4 a8 a12
a1 a5 a9 a13
a2 a6 a10 a14
a3 a7 a11 a15
移位后:
a0 a4 a8 a12
a5 a9 a13 a1
a10 a14 a2 a6
a15 a3 a7 a11
MixColumns(列混合)#

列混合对每一列独立做同样的运算。

对状态矩阵的每一列做 GF(2⁸) 上的矩阵乘法:

举例

[ 02 03 01 01 ] [ s0 ] [ s'0 ]
[ 01 02 03 01 ] × [ s1 ] = [ s'1 ]
[ 01 01 02 03 ] [ s2 ] [ s'2 ]
[ 03 01 01 02 ] [ s3 ] [ s'3 ]
展开得:
s'0 = 02·s0 ⊕ 03·s1 ⊕ 01·s2 ⊕ 01·s3
s'1 = 01·s0 ⊕ 02·s1 ⊕ 03·s2 ⊕ 01·s3
s'2 = 01·s0 ⊕ 01·s1 ⊕ 02·s2 ⊕ 03·s3
s'3 = 03·s0 ⊕ 01·s1 ⊕ 01·s2 ⊕ 02·s3

GF(2⁸) 乘法:

  • 01·x = x
  • 02·x = x << 1,若溢出则异或 0x1B
  • 03·x = 02·x ⊕ x

作用:让每一列的 4 个字节互相影响,提供强扩散性。

这个玩意相当复杂,我们直接跳过,以后再来探索吧。🤣

AddRoundKey(轮密钥加)#

把状态矩阵和轮密钥逐字节异或:

state[i][j] ^= roundKey[i][j]

作用:把密钥混入状态,是唯一依赖密钥的操作。

密钥的编排#

AES-128 从 16 字节密钥扩展出 11 个轮密钥(每个 16 字节,共 176 字节)。

以4字节为一个字。

flowchart LR classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef keyInput fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef wordGroup fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef expand fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef dots fill:#f3f4f6,stroke:#9ca3af,stroke-width:2px,color:#4b5563; classDef roundKey fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d,rx:10,ry:10; K["原始密钥<br/>16 字节 = 4 个字"]:::keyInput W0["W[0..3]"]:::wordGroup Ex1["for i = 4..7<br/>w[i] = w[i-4] ⊕ temp<br/>i%4==0 时 temp 先经 RotWord/SubWord/Rcon"]:::expand W4["W[4..7]"]:::wordGroup Ex2["for i = 8..11<br/>w[i] = w[i-4] ⊕ temp<br/>i%4==0 时 temp 先经 RotWord/SubWord/Rcon"]:::expand W8["W[8..11]"]:::wordGroup DOTS["..."]:::dots W40["W[40..43]"]:::wordGroup RK0["轮密钥 K0"]:::roundKey RK1["轮密钥 K1"]:::roundKey RK2["轮密钥 K2"]:::roundKey RKDOTS["..."]:::dots RK10["轮密钥 K10"]:::roundKey K --> W0 W0 --> Ex1 --> W4 W4 --> Ex2 --> W8 W8 --> DOTS --> W40 W0 --> RK0 W4 --> RK1 W8 --> RK2 W40 --> RK10 RK2 -.-> RKDOTS -.-> RK10 linkStyle default stroke:#6b7280,stroke-width:2px;

细节

flowchart TB classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef wordGroup fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef lastWord fill:#bae6fd,stroke:#0369a1,stroke-width:2px,color:#0c4a6e; classDef step fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef rcon fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef xor fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843; classDef newWord fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d; classDef roundKey fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10; subgraph OneWord["单个 w[i] 的生成(i = 4..43)"] direction TB Start["取 temp = w[i-1]"]:::lastWord Cond{"i % 4 == 0 ?"}:::xor Trans["RotWord → SubWord<br/>→ ⊕ Rcon[i/4]"]:::step Keep["temp 保持 w[i-1]"]:::keep Xor["w[i] = w[i-4] ⊕ temp"]:::xor Out["w[i]"]:::newWord Start --> Cond Cond -- 是 --> Trans Cond -- 否 --> Keep Trans --> Xor Keep --> Xor Xor --> Out end linkStyle default stroke:#6b7280,stroke-width:2px;

比如AES-128

k0 k1 k2 k3 k4 k5 k6 k7 k8 k9 k10 k11 k12 k13 k14 k15
每4字节为一个字
W[0] = [ k0 k1 k2 k3 ]
W[1] = [ k4 k5 k6 k7 ]
W[2] = [ k8 k9 k10 k11 ]
W[3] = [ k12 k13 k14 k15 ]

扩展四个字的三个重要函数

RotWord 循环左移#

把一个 4 字节字循环左移 1 字节

例如

RotWord([09 CF 4F 3C]) = [CF 4F 3C 09]

SubWord 字节代换#

对字中每个字节查 AES S 盒

SubWord([a b c d]) = [SBOX[a] SBOX[b] SBOX[c] SBOX[d]]

Rcon 轮常数#

它属于算法里的常量

Rcon[1] = 0x01
Rcon[2] = 0x02
Rcon[3] = 0x04
Rcon[4] = 0x08
Rcon[5] = 0x10
Rcon[6] = 0x20
Rcon[7] = 0x40
Rcon[8] = 0x80
Rcon[9] = 0x1B
Rcon[10] = 0x36

注意:

  • 从 1 开始编号,不是从 0。
  • 只有 10 个(AES-128 需要 10 轮,正好够用)。
  • 每个值实际上是一个 4 字节字,只有最高字节非零:
Rcon[i] = [ rc_i, 0x00, 0x00, 0x00 ]

其中 rc_i 就是上面那些值。

综合上面三个函数

flowchart TD subgraph Rule["每轮扩展规则(4 个字)"] direction TB A["W[i] = W[i-4] ⊕ SubWord(RotWord(W[i-1])) ⊕ Rcon[j]"]:::step B["W[i+1] = W[i-3] ⊕ W[i]"]:::xor C["W[i+2] = W[i-2] ⊕ W[i+1]"]:::xor D["W[i+3] = W[i-1] ⊕ W[i+2]"]:::xor A --> B --> C --> D end
temp = w[i-1]
if(此时的temp是这组4个字里的最后一个字)
temp = RotWord(temp) // 循环左移一个字节
temp = SubWord(temp) // 每个字节过 S 盒
temp = temp XOR Rcon[i/4] // 异或轮常数
w[i] = w[i-4] XOR temp

三、cpp实现#

常量表#

先来把常量表命名作用域定义一下

// ============================================================
// AES 常量表
// ============================================================
namespace tables
{
// S 盒(256 字节)
constexpr std::array<uint8_t, 256> 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};
// 逆 S 盒
constexpr std::array<uint8_t, 256> 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(只用到前 10 个)
constexpr std::array<uint8_t, 11> RCON = {
0x00, // 不用
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36};
} // namespace tables

GF数学运算定义#

// ============================================================
// GF(2^8) 乘法 公式:c = a · b mod m(x)
// ============================================================
static inline uint8_t gf_mul(uint8_t a, uint8_t b)
{
uint8_t p = 0;//p是累加器,返回值也是他。
for (int i = 0; i < 8; i++)
{//循环8次,意味着是bit视角
if (b & 1)//每次从b的最后一个bit开始看,如果 b 当前最低位是 1,就把 a 异或进 p。
p ^= a;//相当于普通乘法里的“这一位有值,我们给他加上,在GF(2^8)里异或就是加法。
uint8_t hi = a & 0x80; //看 a 的最高位是不是 1。hi 只用于判断,后面左移后要异或 0x1B。
a <<= 1; //a 左移一位,相当于“乘以 x(也就是乘以 2)”
if (hi) //如果刚才最高位是 1,左移后溢出了 x^8 项。
a ^= 0x1B; // AES 不可约多项式 x^8 + x^4 + x^3 + x + 1
b >>= 1; //b 右移一位,处理下一位。
}
return p;
}
// 快速版:乘 2 和乘 3
static inline uint8_t xtime(uint8_t x)
{
return (uint8_t)((x << 1) ^ ((x & 0x80) ? 0x1B : 0x00));
}
static inline uint8_t mul2(uint8_t x) { return xtime(x); }
static inline uint8_t mul3(uint8_t x) { return xtime(x) ^ x; }

妈的,太特么难了!

PKCS7填充#

// ============================================================
// PKCS#7 填充(块大小 16)
// ============================================================
namespace pkcs7
{
std::vector<uint8_t> pad(const std::vector<uint8_t> &data, size_t blockSize)
{
size_t padLen = blockSize - (data.size() % blockSize);
std::vector<uint8_t> out = data;
out.insert(out.end(), padLen, static_cast<uint8_t>(padLen));
return out;
}
std::vector<uint8_t> unpad(const std::vector<uint8_t> &data)
{
if (data.empty())
throw std::runtime_error("empty data");
uint8_t padLen = data.back();
if (padLen == 0 || padLen > 16 || padLen > data.size())
{
throw std::runtime_error("invalid PKCS#7 padding");
}
for (size_t i = data.size() - padLen; i < data.size(); ++i)
{
if (data[i] != padLen)
{
throw std::runtime_error("invalid PKCS#7 padding");
}
}
return std::vector<uint8_t>(data.begin(), data.end() - padLen);
}
} // namespace pkcs7

核心类#

// ============================================================
// AES 核心类(AES-128)
// ============================================================
class AES128
{
public:
static constexpr size_t BLOCK_SIZE = 16; //分组大小
static constexpr size_t KEY_SIZE = 16;
static constexpr int NR = 10; // 轮数
explicit AES128(const std::vector<uint8_t> &key)
{
if (key.size() != KEY_SIZE)
{
throw std::invalid_argument("AES-128 key must be 16 bytes");
}
keyExpansion(key.data());//特妈的上来就扩展,好好好。我们直接看到第90行
}
void encryptBlock(uint8_t block[BLOCK_SIZE]) const
{
uint8_t state[4][4];
loadState(state, block);
addRoundKey(state, 0);
for (int round = 1; round < NR; ++round)
{
subBytes(state);
shiftRows(state);
mixColumns(state);
addRoundKey(state, round);
}
// 最后一轮:无 MixColumns
subBytes(state);
shiftRows(state);
addRoundKey(state, NR);
storeState(state, block);
}
void decryptBlock(uint8_t block[BLOCK_SIZE]) const
{
uint8_t state[4][4];
loadState(state, block);
addRoundKey(state, NR);
for (int round = NR - 1; round >= 1; --round)
{
invShiftRows(state);
invSubBytes(state);
addRoundKey(state, round);
invMixColumns(state);
}
invShiftRows(state);
invSubBytes(state);
addRoundKey(state, 0);
storeState(state, block);
}
private:
uint8_t roundKeys_[11][16]; // 11 个轮密钥
// --------------------------------------------------------
// 状态矩阵的加载 / 存储(列优先)
// --------------------------------------------------------
static void loadState(uint8_t state[4][4], const uint8_t block[16])
{
for (int i = 0; i < 16; ++i)
{
state[i % 4][i / 4] = block[i];
}
}
static void storeState(uint8_t state[4][4], uint8_t block[16])
{
for (int i = 0; i < 16; ++i)
{
block[i] = state[i % 4][i / 4];
}
}
// --------------------------------------------------------
// 密钥扩展:从 16 字节密钥生成 11 个轮密钥
// --------------------------------------------------------
void keyExpansion(const uint8_t key[16])
{
using namespace tables;
uint8_t w[44][4]; // 44 个字,每个字 4 字节,一个轮密钥4个字,44也就是11个轮密钥
// 前 4 个字 = 原始密钥
for (int i = 0; i < 4; ++i)
{
w[i][0] = key[4 * i + 0];
w[i][1] = key[4 * i + 1];
w[i][2] = key[4 * i + 2];
w[i][3] = key[4 * i + 3];
}
/*
举例
key = 2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c
w[0] = 2b 7e 15 16
w[1] = 28 ae d2 a6
w[2] = ab f7 15 88
w[3] = 09 cf 4f 3c
*/
// 扩展出后面 40 个字
for (int i = 4; i < 44; ++i)
{
uint8_t temp[4];
std::memcpy(temp, w[i - 1], 4);
if (i % 4 == 0)
/*
为什么 i % 4 == 0 才做
每 4 个字是一组轮密钥。
每组第一个字需要和上一组“断开”,所以做 RotWord + SubWord + Rcon。
后面 3 个字只需要和前一个字、前 4 个字异或,保持链式关系。
*/
{
// RotWord:循环左移 1 字节
uint8_t t = temp[0];
temp[0] = temp[1];
temp[1] = temp[2];
temp[2] = temp[3];
temp[3] = t;
// SubWord:每字节查 S 盒
for (int j = 0; j < 4; ++j)
{
temp[j] = SBOX[temp[j]];
}
// 异或 Rcon
temp[0] ^= RCON[i / 4];
}
// W[i] = W[i-4] ⊕ temp
for (int j = 0; j < 4; ++j)
{
w[i][j] = w[i - 4][j] ^ temp[j];
}
}
// 把 44 个字拼成 11 个轮密钥
for (int r = 0; r <= NR; ++r)
{
for (int c = 0; c < 4; ++c)
{
for (int j = 0; j < 4; ++j)
{
roundKeys_[r][4 * c + j] = w[4 * r + c][j];
}
}
}
}
// --------------------------------------------------------
// 四个核心操作
// --------------------------------------------------------
// SubBytes:字节替换
// 对 state 中每个字节查 S 盒,做非线性替换
// 这是 AES 里唯一的非线性操作,抵抗线性和差分分析
static void subBytes(uint8_t state[4][4])
{
using namespace tables;
for (int i = 0; i < 4; ++i) // 遍历 4 行
for (int j = 0; j < 4; ++j) // 遍历 4 列
state[i][j] = SBOX[state[i][j]]; // 用当前字节做索引查 S 盒
}
// InvSubBytes:逆字节替换
// 解密时用,查逆 S 盒,把字节还原
static void invSubBytes(uint8_t state[4][4])
{
using namespace tables;
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
state[i][j] = INV_SBOX[state[i][j]]; // 查逆 S 盒
}
// ShiftRows:行移位
// 第 0 行不动,第 1 行左移 1,第 2 行左移 2,第 3 行左移 3
// 作用:让不同列之间的字节互相混合,增强扩散
static void shiftRows(uint8_t state[4][4])
{
// 第 1 行左移 1 字节
// [a b c d] → [b c d a]
uint8_t t = state[1][0];
state[1][0] = state[1][1];
state[1][1] = state[1][2];
state[1][2] = state[1][3];
state[1][3] = t;
// 第 2 行左移 2 字节
// [a b c d] → [c d a b]
// 等价于交换前两个和后两个
t = state[2][0];
state[2][0] = state[2][2];
state[2][2] = t;
t = state[2][1];
state[2][1] = state[2][3];
state[2][3] = t;
// 第 3 行左移 3 字节,等价于右移 1 字节
// [a b c d] → [d a b c]
t = state[3][3];
state[3][3] = state[3][2];
state[3][2] = state[3][1];
state[3][1] = state[3][0];
state[3][0] = t;
}
// InvShiftRows:逆行移位
// 解密时用,和 ShiftRows 方向相反
// 第 0 行不动,第 1 行右移 1,第 2 行右移 2,第 3 行右移 3
static void invShiftRows(uint8_t state[4][4])
{
// 第 1 行右移 1 字节
// [a b c d] → [d a b c]
uint8_t t = state[1][3];
state[1][3] = state[1][2];
state[1][2] = state[1][1];
state[1][1] = state[1][0];
state[1][0] = t;
// 第 2 行右移 2 字节
// [a b c d] → [c d a b]
// 和加密时的交换一样,因为交换两次就还原了
t = state[2][0];
state[2][0] = state[2][2];
state[2][2] = t;
t = state[2][1];
state[2][1] = state[2][3];
state[2][3] = t;
// 第 3 行右移 3 字节,等价于左移 1 字节
// [a b c d] → [b c d a]
t = state[3][0];
state[3][0] = state[3][1];
state[3][1] = state[3][2];
state[3][2] = state[3][3];
state[3][3] = t;
}
// MixColumns:列混合
// 对每一列做矩阵乘法,矩阵是固定的:
// [02 03 01 01]
// [01 02 03 01]
// [01 01 02 03]
// [03 01 01 02]
// 乘法在 GF(2^8) 上进行,mul2 = 乘 2,mul3 = 乘 3
// 作用:让一列内的 4 个字节互相影响,增强扩散
static void mixColumns(uint8_t state[4][4])
{
for (int c = 0; c < 4; ++c) // 遍历每一列
{
// 取出这一列的 4 个字节
uint8_t a0 = state[0][c];
uint8_t a1 = state[1][c];
uint8_t a2 = state[2][c];
uint8_t a3 = state[3][c];
// 矩阵乘法的第 0 行:02*a0 + 03*a1 + 01*a2 + 01*a3
// GF(2^8) 里加法就是异或,01*a = a
state[0][c] = mul2(a0) ^ mul3(a1) ^ a2 ^ a3;
// 第 1 行:01*a0 + 02*a1 + 03*a2 + 01*a3
state[1][c] = a0 ^ mul2(a1) ^ mul3(a2) ^ a3;
// 第 2 行:01*a0 + 01*a1 + 02*a2 + 03*a3
state[2][c] = a0 ^ a1 ^ mul2(a2) ^ mul3(a3);
// 第 3 行:03*a0 + 01*a1 + 01*a2 + 02*a3
state[3][c] = mul3(a0) ^ a1 ^ a2 ^ mul2(a3);
}
}
// InvMixColumns:逆列混合
// 解密时用,矩阵是加密矩阵的逆:
// [0E 0B 0D 09]
// [09 0E 0B 0D]
// [0D 09 0E 0B]
// [0B 0D 09 0E]
// 每个系数都要在 GF(2^8) 上乘,所以用通用的 gf_mul
static void invMixColumns(uint8_t state[4][4])
{
for (int c = 0; c < 4; ++c)
{
uint8_t a0 = state[0][c];
uint8_t a1 = state[1][c];
uint8_t a2 = state[2][c];
uint8_t a3 = state[3][c];
// 第 0 行:0E*a0 + 0B*a1 + 0D*a2 + 09*a3
state[0][c] = gf_mul(a0, 0x0E) ^ gf_mul(a1, 0x0B) ^ gf_mul(a2, 0x0D) ^ gf_mul(a3, 0x09);
// 第 1 行:09*a0 + 0E*a1 + 0B*a2 + 0D*a3
state[1][c] = gf_mul(a0, 0x09) ^ gf_mul(a1, 0x0E) ^ gf_mul(a2, 0x0B) ^ gf_mul(a3, 0x0D);
// 第 2 行:0D*a0 + 09*a1 + 0E*a2 + 0B*a3
state[2][c] = gf_mul(a0, 0x0D) ^ gf_mul(a1, 0x09) ^ gf_mul(a2, 0x0E) ^ gf_mul(a3, 0x0B);
// 第 3 行:0B*a0 + 0D*a1 + 09*a2 + 0E*a3
state[3][c] = gf_mul(a0, 0x0B) ^ gf_mul(a1, 0x0D) ^ gf_mul(a2, 0x09) ^ gf_mul(a3, 0x0E);
}
}
// AddRoundKey:轮密钥加
// 把当前 state 和本轮轮密钥逐字节异或
// 这是唯一用到密钥的步骤,其他三步都是固定的
void addRoundKey(uint8_t state[4][4], int round) const
{
for (int i = 0; i < 4; ++i) // 行
{
for (int j = 0; j < 4; ++j) // 列
{
// roundKeys_ 是按列优先存的:
// roundKeys_[round][4*j + i] 对应 state[i][j]
state[i][j] ^= roundKeys_[round][4 * j + i];
}
}
}
};

四、ECB模式#

// ============================================================
// ECB 模式
// ============================================================
class AESECB
{
public:
explicit AESECB(const std::vector<uint8_t> &key) : aes_(key) {}
std::vector<uint8_t> encrypt(const std::vector<uint8_t> &plaintext) const
{
std::vector<uint8_t> data = pkcs7::pad(plaintext, AES128::BLOCK_SIZE);
std::vector<uint8_t> out(data.size());
for (size_t i = 0; i < data.size(); i += AES128::BLOCK_SIZE)
{
uint8_t block[16];
std::memcpy(block, data.data() + i, 16);
aes_.encryptBlock(block);
std::memcpy(out.data() + i, block, 16);
}
return out;
}
std::vector<uint8_t> decrypt(const std::vector<uint8_t> &ciphertext) const
{
if (ciphertext.size() % AES128::BLOCK_SIZE != 0)
{
throw std::invalid_argument("ciphertext size must be multiple of 16");
}
std::vector<uint8_t> out(ciphertext.size());
for (size_t i = 0; i < ciphertext.size(); i += AES128::BLOCK_SIZE)
{
uint8_t block[16];
std::memcpy(block, ciphertext.data() + i, 16);
aes_.decryptBlock(block);
std::memcpy(out.data() + i, block, 16);
}
return pkcs7::unpad(out);
}
private:
AES128 aes_;
};

五、CBC模式#

// ============================================================
// CBC 模式
// ============================================================
class AESCBC
{
public:
// 构造函数
// key:AES 密钥(16 字节)
// iv :初始向量(16 字节),CBC 模式必须提供
AESCBC(const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv)
: aes_(key) // 用 key 初始化内部 AES128 对象
{
if (iv.size() != AES128::BLOCK_SIZE) // IV 必须是 16 字节
{
throw std::invalid_argument("IV must be 16 bytes");
}
std::memcpy(iv_, iv.data(), 16); // 把 IV 拷贝到成员变量
}
// 加密
// 输入:明文(任意长度)
// 输出:密文(长度是 16 的整数倍)
std::vector<uint8_t> encrypt(const std::vector<uint8_t> &plaintext) const
{
// 1. PKCS#7 填充,把明文补到 16 的整数倍
std::vector<uint8_t> data = pkcs7::pad(plaintext, AES128::BLOCK_SIZE);
// 2. 输出缓冲区,和填充后的明文一样长
std::vector<uint8_t> out(data.size());
// 3. prev 保存"上一块密文",初始为 IV
uint8_t prev[16];
std::memcpy(prev, iv_, 16);
// 4. 按 16 字节一块,逐块加密
for (size_t i = 0; i < data.size(); i += AES128::BLOCK_SIZE)
{
uint8_t block[16];
// 4.1 明文块和 prev 异或
// CBC 的核心:当前明文先和上一块密文异或,再加密
for (size_t j = 0; j < 16; ++j)
{
block[j] = data[i + j] ^ prev[j];
}
// 4.2 用 AES 加密这一块
aes_.encryptBlock(block);
// 4.3 把密文写进输出
std::memcpy(out.data() + i, block, 16);
// 4.4 更新 prev 为当前密文块,供下一块使用
std::memcpy(prev, block, 16);
}
return out;
}
// 解密
// 输入:密文(长度必须是 16 的整数倍)
// 输出:明文(已去掉 PKCS#7 填充)
std::vector<uint8_t> decrypt(const std::vector<uint8_t> &ciphertext) const
{
// 1. 检查密文长度是不是 16 的整数倍
if (ciphertext.size() % AES128::BLOCK_SIZE != 0)
{
throw std::invalid_argument("ciphertext size must be multiple of 16");
}
// 2. 输出缓冲区,长度和密文一样
// 解密后还会包含填充字节,最后再去掉
std::vector<uint8_t> out(ciphertext.size());
// 3. prev 保存"上一块密文",初始为 IV
uint8_t prev[16];
std::memcpy(prev, iv_, 16);
// 4. 按 16 字节一块,逐块解密
for (size_t i = 0; i < ciphertext.size(); i += AES128::BLOCK_SIZE)
{
uint8_t block[16];
// 4.1 取出当前密文块
std::memcpy(block, ciphertext.data() + i, 16);
// 4.2 备份当前密文块
// 因为下面要解密 block,解密后原始密文就没了,
// 但下一轮需要用它作为 prev,所以先存一份
uint8_t cipherBlock[16];
std::memcpy(cipherBlock, block, 16);
// 4.3 AES 解密
aes_.decryptBlock(block);
// 4.4 解密结果和 prev 异或,得到明文块
// CBC 解密:明文 = AES_decrypt(密文) XOR 上一块密文
for (size_t j = 0; j < 16; ++j)
{
out[i + j] = block[j] ^ prev[j];
}
// 4.5 更新 prev 为当前密文块
std::memcpy(prev, cipherBlock, 16);
}
// 5. 去掉 PKCS#7 填充,返回真正的明文
return pkcs7::unpad(out);
}
private:
AES128 aes_; // 内部 AES 对象,负责单块加解密
uint8_t iv_[16]; // 保存 IV
};

ok,就到这里吧,至于GCM模式,这个得新开一张说了。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

AES从原理到cpp实现
https://fatdog.20060113.xyz/posts/aes/
作者
神秘大胖狗
发布于
2026-09-18
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00