AES算法入门

发布: (2025年12月1日 GMT+8 06:43)
8 min read
原文: Dev.to

Source: Dev.to

概述

AES 是一种对称密钥加密算法,被认为极其安全、实现非常简单,并且在实际中得到广泛使用。
本指南教你如何在 C 语言中实现其 128 位 CTR 模式 变体。

步骤:

  1. 随机生成一个 128 位的 密码键
  2. 将其秘密共享给你的朋友(例如通过 Diffie‑Hellman —— 本文不涉及)。
  3. 使用该密钥加密你的消息。
  4. 发送加密后的消息。
  5. 你的朋友使用相同的密钥解密。

测试数据可在 YouTube 查看。


实现 AES128

核心函数使用 16 字节的密钥对单个 16 字节块进行加密:

void AES128( uint8_t* block, uint8_t* cipherKey) { }

它是若干辅助函数的流水线,这些函数按顺序作用于块。

AES overview

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}

4x4 block

*注意:*块采用 列主序 存储(第二个元素位于第一个元素的下方)。

步骤 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) { }

重要提示: 分配内存的函数(getRowgetCol)在使用后必须释放返回的指针。

步骤 5 – 旋转操作

Rotate illustration

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 函数

ShiftRows diagram

步骤 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

MixColumns diagram

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 块。

Key expansion overview

列记作 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 模式)

CTR mode diagram

加密步骤:

  1. 生成一个随机的 64 位 nonce
  2. 将明文划分为 16 字节块。
  3. 对于每个块 i,构造 16 字节的密钥流:
    • 前 8 字节 = nonce
    • 后 8 字节 = i(以 8 字节小端序表示)
    • 使用密码键对该密钥流运行 AES128
  4. 将密钥流与明文块异或。
  5. 将 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 的加密输出,或相反地进行解密。

Back to Blog

相关文章

阅读更多 »