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 密钥,加密过程分为:
- 密钥扩展:把 16 字节密钥扩展成 11 个轮密钥,每个 16 字节。
- 初始轮密钥加:
AddRoundKey(K0)。 - 第 1~9 轮:
SubBytesShiftRowsMixColumnsAddRoundKey(Kr)
- 第 10 轮,不执行
MixColumns:SubBytesShiftRowsAddRoundKey(K10)
| 属性 | 值 |
|---|---|
| 分组大小 | 128 bit(16 字节) |
| 密钥长度 | 128 / 192 / 256 bit |
| 轮数 | 10 / 12 / 14(对应密钥长度) |
| 结构 | SP 网络(不是 Feistel) |
| 状态表示 | 4×4 字节矩阵 |
| 安全性 | ✅ 至今未被有效攻破 |
AES 与 DES 的本质区别:
| 维度 | DES | AES |
|---|---|---|
| 结构 | Feistel | SP 网络 |
| 分组 | 64 bit | 128 bit |
| 每轮操作 | 只处理右半 | 处理整个状态 |
| 核心非线性 | S 盒(6→4 bit) | S 盒(8→8 bit) |
| 数学基础 | 位运算 | GF(2⁸) 有限域 |
| 解密 | 结构相同 | 结构不同(逆操作) |
AES 没有 Feistel 的对称性——解密必须实现每一步的逆操作,不能像 DES 那样复用加密结构。
二、原理
加密
解密
明文的加密
由于是分组加密,所以输入的明文数据一定是16字节的倍数,不足需要填充。
我们先来看看明文的处理过程。
1.状态矩阵
AES 把 16 字节明文排成 4×4 矩阵,按列优先填充:
明文 = [b0, b1, b2, ..., b15]
状态矩阵: col0 col1 col2 col3row0 b0 b4 b8 b12row1 b1 b5 b9 b13row2 b2 b6 b10 b14row3 b3 b7 b11 b15举例:明文 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
状态矩阵:00 44 88 CC11 55 99 DD22 66 AA EE33 77 BB FF记住:AES是列优先
2.四个核心操作
SubBytes(字节替换)
对状态矩阵每个字节独立查一个固定的 S 盒(256 字节的表)。
S 盒构造分两步:
- 在 GF(2⁸) 上求乘法逆元(0 映射到 0)
- 做一次仿射变换(affine transformation)
S 盒是 AES 唯一的非线性部件,是安全性的核心,好复杂,想深入的大佬可以研究一下,我不会就不讲了。
下面是S盒的样子,一共256字节。

ShiftRows(行移位)
对状态矩阵的每一行循环左移,移位量按行递增:
第 0 行:不移第 1 行:左移 1 字节第 2 行:左移 2 字节第 3 行:左移 3 字节移位前:a0 a4 a8 a12a1 a5 a9 a13a2 a6 a10 a14a3 a7 a11 a15
移位后:a0 a4 a8 a12a5 a9 a13 a1a10 a14 a2 a6a15 a3 a7 a11MixColumns(列混合)
列混合对每一列独立做同样的运算。
对状态矩阵的每一列做 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·s3s'1 = 01·s0 ⊕ 02·s1 ⊕ 03·s2 ⊕ 01·s3s'2 = 01·s0 ⊕ 01·s1 ⊕ 02·s2 ⊕ 03·s3s'3 = 03·s0 ⊕ 01·s1 ⊕ 01·s2 ⊕ 02·s3GF(2⁸) 乘法:
01·x = x02·x = x << 1,若溢出则异或0x1B03·x = 02·x ⊕ x
作用:让每一列的 4 个字节互相影响,提供强扩散性。
这个玩意相当复杂,我们直接跳过,以后再来探索吧。🤣
AddRoundKey(轮密钥加)
把状态矩阵和轮密钥逐字节异或:
state[i][j] ^= roundKey[i][j]作用:把密钥混入状态,是唯一依赖密钥的操作。
密钥的编排
AES-128 从 16 字节密钥扩展出 11 个轮密钥(每个 16 字节,共 176 字节)。
以4字节为一个字。
细节
比如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] = 0x01Rcon[2] = 0x02Rcon[3] = 0x04Rcon[4] = 0x08Rcon[5] = 0x10Rcon[6] = 0x20Rcon[7] = 0x40Rcon[8] = 0x80Rcon[9] = 0x1BRcon[10] = 0x36注意:
- 从 1 开始编号,不是从 0。
- 只有 10 个(AES-128 需要 10 轮,正好够用)。
- 每个值实际上是一个 4 字节字,只有最高字节非零:
Rcon[i] = [ rc_i, 0x00, 0x00, 0x00 ]其中 rc_i 就是上面那些值。
综合上面三个函数
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 tablesGF数学运算定义
// ============================================================// 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 和乘 3static 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模式,这个得新开一张说了。
部分信息可能已经过时









