AES算法入门
Source: Dev.to
概述
AES 是一种对称密钥加密算法,被认为极其安全、实现非常简单,并且在实际中得到广泛使用。
本指南教你如何在 C 语言中实现其 128 位 CTR 模式 变体。
步骤:
- 随机生成一个 128 位的 密码键。
- 将其秘密共享给你的朋友(例如通过 Diffie‑Hellman —— 本文不涉及)。
- 使用该密钥加密你的消息。
- 发送加密后的消息。
- 你的朋友使用相同的密钥解密。
测试数据可在 YouTube 查看。
实现 AES128
核心函数使用 16 字节的密钥对单个 16 字节块进行加密:
void AES128( uint8_t* block, uint8_t* cipherKey) { }
它是若干辅助函数的流水线,这些函数按顺序作用于块。

11 次 “Apply Key” 步骤各自使用不同的 轮密钥,这些轮密钥通过 getNthRoundKey 从主密码键派生。
指南的后半部分定义了更高层的辅助函数,这些函数会反复调用 AES128:
uint8_t* encrypt(uint8_t* plainText, uint64_t lengthInBytes, uint8_t* cipherKey) { }
uint8_t* decrypt(uint8_t* encryptedText, uint64_t lengthInBytes, uint8_t* cipherKey) { }
块
块是 4 × 4 的字节矩阵(0‑255)。在 C 中它们以平铺数组 uint8_t[16](栈)或 uint8_t*(堆)的形式存储。示例块:{0,1,2,…,15}。

*注意:*块采用 列主序 存储(第二个元素位于第一个元素的下方)。
步骤 1 – 打印块
void printBlock(uint8_t* block) { }
步骤 2 – 设置元素
void setElement(uint8_t* block, int nthRow, int nthCol, uint8_t element) { }
行和列都是四字节的平铺数组。行的第一个元素位于最左侧;列的第一个元素位于最顶部。
步骤 3 – 提取行 / 列
uint8_t* getRow(uint8_t* block, int nthRow) {
// malloc storage for a new row
// copy values from block to the new storage
// return the newly allocated storage
}
uint8_t* getCol(uint8_t* block, int nthCol) {
// same as above for a column
}
步骤 4 – 打印行 / 列
void printRow(uint8_t* row) { }
void printCol(uint8_t* col) { }
重要提示: 分配内存的函数(
getRow、getCol)在使用后必须释放返回的指针。
步骤 5 – 旋转操作

void rotateLeftRow(uint8_t* row) { }
void rotateUpCol(uint8_t* col) { }
// Operate in‑place; do not allocate new memory.
步骤 6 – 将行 / 列写回块中
void setRow(uint8_t* block, uint8_t* row, int nthRow) {
// copy row into the block (no allocations)
}
void setCol(uint8_t* block, uint8_t* col, int nthCol) {
// copy column into the block (no allocations)
}
这些原语构成了 AES‑128 流水线的基础;在继续之前请先验证它们。
subBytes 函数
S‑盒常量(索引 0 → 255)为:
{99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,
202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,
183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,
4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,
9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,
83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,
208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,
81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,
205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,
96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,
224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,
231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,
186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,
112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,
225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,
140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22}
步骤 7 – S‑盒访问器
uint8_t sbox(uint8_t i) { }
步骤 8 – 对状态应用 S‑盒
void subBytes(uint8_t* state) {
// replace each byte with sbox(byte)
// operate in‑place, no allocations
}
shiftRows 函数

步骤 9 – 实现 shiftRows
void shiftRows(uint8_t* state) {
// For each row:
// 1. getRow()
// 2. rotateLeftRow() the appropriate number of positions
// 3. setRow()
// Free any allocated rows.
}
mixColumns 函数
首先需要一个伽罗瓦域乘法辅助函数。我们提供了预计算表。
表格
- b = 1 的表 – 按顺序列出 0‑255 的值。
- b = 2 的表 – 原文内容。
- b = 3 的表 – 原文内容。
步骤 10 – gfmul
uint8_t gfmul(uint8_t a, uint8_t b) {
// a ∈ [0,255], b ∈ {1,2,3}
// return the corresponding entry from the appropriate table
}
步骤 11 – mixColumns

void mixColumns(uint8_t* state) {
// 1. Allocate a temporary 4×4 block for the output.
// 2. For each output element (row r, col c):
// - fetch row r of the multiplication matrix,
// - fetch column c of the input state,
// - compute Σ gfmul(matrix_elem, state_elem) (xor the results),
// - store in the temporary block.
// 3. Copy the temporary block back into `state`.
// 4. Free the temporary block.
}
useRoundKey 函数
步骤 12 – 将状态与轮密钥异或
void useRoundKey(uint8_t* state, uint8_t* roundKey) {
// for i = 0..15: state[i] ^= roundKey[i];
}
密钥扩展
11 个轮密钥是从原始密码键派生的 4 × 4 块。

列记作 w0, w1, …, w43。
轮密钥 0 直接复制自密码键。
步骤 13 – 获取轮密钥的某一列
uint8_t* getNthColOfRoundKey(int nthCol, uint8_t* cipherKey) {
uint8_t rcon[] = {1,2,4,8,16,32,64,128,27,54};
if (/* nthCol is 0‑3 */) {
// return the corresponding column from cipherKey using getCol()
} else if (/* nthCol ≡ 1 (mod 4) */) {
// allocate new column
// col = getNthColOfRoundKey(nthCol-1) XOR getNthColOfRoundKey(nthCol-4)
// return col
} else {
// allocate new column
// tmp = getNthColOfRoundKey(nthCol-1)
// rotateUpCol(tmp)
// col = tmp XOR getNthColOfRoundKey(nthCol-4)
// col[0] ^= rcon[nthCol/4 - 1]; // apply rcon
// return col
}
}
步骤 14 – 获取完整的轮密钥
uint8_t* getNthRoundKey(int n, uint8_t* cipherKey) {
// allocate 16‑byte block
// for each column i = 0..3:
// copy getNthColOfRoundKey(4*n + i) into the block
// return the block
}
步骤 15 – 完成 AES128
void AES128(uint8_t* block, uint8_t* cipherKey) {
// 1. Initial AddRoundKey (round 0)
// 2. For rounds 1‑9:
// subBytes()
// shiftRows()
// mixColumns()
// useRoundKey() with roundKey = getNthRoundKey(round, cipherKey)
// 3. Final round (10):
// subBytes()
// shiftRows()
// useRoundKey() with roundKey = getNthRoundKey(10, cipherKey)
// Free any allocated round keys.
}
将密码键转化为密钥流(CTR 模式)

加密步骤:
- 生成一个随机的 64 位 nonce。
- 将明文划分为 16 字节块。
- 对于每个块
i,构造 16 字节的密钥流:- 前 8 字节 = nonce
- 后 8 字节 =
i(以 8 字节小端序表示) - 使用密码键对该密钥流运行
AES128。
- 将密钥流与明文块异或。
- 将 nonce 附加到密文末尾。
解密过程相同,只是从最后 8 字节提取 nonce。
步骤 16 – 从 64 位数中提取字节
uint8_t getNthByte(uint64_t number, int n) {
// returns the n‑th least‑significant byte (0‑based)
return (number >> (n * 8)) & 0xFF;
}
步骤 17 – 为块生成密钥流
uint8_t* getKeystream(uint8_t* cipherKey, uint64_t nthBlock, uint8_t* nonce) {
// allocate 16‑byte array `keystream`
// copy nonce (8 bytes) into keystream[0..7]
// convert nthBlock to 8‑byte array and copy into keystream[8..15]
// AES128(keystream, cipherKey);
// return keystream;
}
步骤 18 – 加密消息
uint8_t* encrypt(uint8_t* plainText, uint64_t lengthInBytes, uint8_t* cipherKey) {
// 1. allocate 8‑byte nonce and fill with random data
// 2. allocate output buffer of size lengthInBytes + 8 (nonce)
// 3. for each 16‑byte block:
// keystream = getKeystream(cipherKey, blockIndex, nonce);
// xor plaintext block with keystream → ciphertext block
// free keystream
// 4. copy nonce to the last 8 bytes of the output
// 5. return the output buffer
}
步骤 19 – 解密消息
uint8_t* decrypt(uint8_t* encryptedText, uint64_t lengthInBytes, uint8_t* cipherKey) {
// 1. allocate 8‑byte nonce
// 2. copy the last 8 bytes of encryptedText into nonce
// 3. allocate output buffer of size lengthInBytes - 8
// 4. for each 16‑byte block:
// keystream = getKeystream(cipherKey, blockIndex, nonce);
// xor ciphertext block with keystream → plaintext block
// free keystream
// 5. return the plaintext buffer
}
步骤 20 – (可选)基于文件的工具
将程序改写为读取明文文件、写入包含 nonce 的加密输出,或相反地进行解密。