Note

最近看到了一个比较浅显易懂的 AES 介绍视频:(只用加法乘法)手撕美国 AES 高级加密标准过程,比较好看进去,但是可能有错误。
以及一个博客,讲解思路与之类似:AES 算法详解

1. 理论介绍

AES(Advanced Encryption Standard,高级加密标准)是一种经典的块加密算法,用以取代 DES (Data Encryption Standard)。现在,AES 已经成为对称加密算法中最流行的算法之一。

AES 的明文区块长度固定为 128 bits(16 字节),密钥长度则可以是 128、192 或 256 bits,根据密钥长度的不同将 AES 分为 AES-128、AES-192 和 AES-256,加密轮数分别为 10 轮、12 轮和 14 轮。加密过程中使用的轮密钥是由 Rijndael 密钥生成方案 产生的。如果明文长度不是区块长度的整数倍,则必须使用填充将其填充到整数倍,一种主流的填充方法是 PKCS #7。

AES 加密过程是在一个 4×4 的字节矩阵上运行的,这个矩阵又称为 “ (state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个 byte,即 8 bits)。

aes_data_unit

(来源:AES - CTF Wiki

一般来讲,我们会使用十六进制对明文进行编码。

AES 每轮的加密主要分为 4 个操作:

  • 字节替换 (SubBytes)
  • 行移位 (ShiftRows)
  • 列混淆 (MixColumns)
  • 轮密钥加 (AddRoundKey)

其中,进行第一轮加密前,需要进行一次 “轮密钥加” 以生成第一轮加密所需的输入;最后一轮执行 “列混淆” 操作。

aes_details

(来源:[原创] 128 位 AES 加密算法图解 - 看雪,注意上面的列混淆的矩阵乘法等号左边的列向量应该在右边。)

1.0 种子密钥加

其实是就是轮密钥加,可以往后面看看。这里是把明文矩阵和种子密钥异或得到第一轮的状态矩阵。

1.1 字节替换

简单说就是一个映射操作,以防止出现重复元素。

SubBytes

如上图,a[0,0]0x12, 查 S 盒 [1,2] 处得到 0xC9a[0,1]0xAB,查 S 盒 [A,B] 处,也就是 [10,11],得到 0x62

如果是逆 S 盒,那方法也相同,以 0xC9 为例,查逆 S 盒 [C,9][13,9],对应表中值为 0x12

1.2 行移位

行位移将输入的数据作为一个 4x4 矩阵进行处理,可以理解为左循环位移操作。

正向行位移,第一行保持不变,第二行左移 1 字节 ,第三行左移 2 字节,第四行左移 3 字节

813468_QC94QRQKGMUEYPR

逆向行位移,第一行保持不变,第二行右移 1 字节,第三行右移 2 字节,第四行右移 3 字节

813468_NN4U4URSVQY4W69

1.3 列混淆

列混合变换就是矩阵相乘,行位移后的矩阵和修补矩阵 (fixed matrix) 做矩阵相乘,得出输出列。修补矩阵是固定的。

813468_UES487VAAZPKDFS

解密时,逆向列混淆的修补矩阵与正向列混淆的修补矩阵互为逆矩阵。

813468_ZCDXZKFMZA7K9SD

1.4 轮密钥加

根据传入的轮数,将本轮列混淆后的加密结果与对应的轮密钥 k 异或。

AddRoundKey

AES (step-by-step) 可以看到每一轮加密的示例过程。

1.5 轮密钥生成

每轮的轮密钥是以种子密钥为基础生成的,称为密钥扩展 (KeyExpansion)。密钥扩展按列计算,每四列构成一个轮密钥。进行 NN 轮加密就需要 NN 个轮密钥,最终得到 4(N+1)4(N+1) 列密钥(因为包含了种子密钥)

令种子密钥的第一列为第 0 列,按升序排序。

如果列数不是 4 的倍数,那么计算公式为:

Rowi=Rowi4Rowi1Row_{i}=Row_{i-4}⊗Row_{i-1}

如果列数是 4 的倍数,计算公式会复杂一点:

首先,将 Rowi1Row_{i-1} 的最上方的元素放到该列最后的元素之后,使用 S 盒进行字节替换,得到 Rowi1Row'_{i-1}

然后按照下面的公式计算列:

Rowi=Rowi1Rowi1ConstRowi4Row_{i}=Row_{i-1}⊗Row'_{i-1}⊗ConstRow_{\frac{i}{4}}

其中 ConstRowConstRow 是给定的轮常量。

2. C++ 实现 (AES-256)

2.1 常量定义

首先,定义必要的常量和映射表。

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
#include <iostream>
#include <vector>
#include <array>

// Define constants for AES-256
const int AES_BLOCK_SIZE = 16; // Block size in bytes (128 bits)
const int AES_KEY_SIZE_256 = 32; // Key size for AES-256 in bytes (256 bits)
const int AES_ROUNDS_256 = 14; // Number of rounds for AES-256

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

// Rcon (Round Constant) for Key Expansion
const std::array<unsigned char, 15> Rcon = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a
};

2.2 操作辅助函数

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

// Helper function for byte substitution using S-box
unsigned char SubBytes_single(unsigned char byte) {
return sbox[byte];
}

// Helper function for XORing byte arrays
void xor_bytes(unsigned char *a, const unsigned char *b, int len) {
for (int i = 0; i < len; ++i) {
a[i] ^= b[i];
}
}

// Helper function for left rotation of a 32-bit word (used in Key Expansion)
unsigned int rotWord(unsigned int word) {
return (word << 8) | (word >> 24);
}

// Helper function for applying S-box to a 32-bit word (used in Key Expansion)
unsigned int subWord(unsigned int word) {
unsigned char bytes[4];
for (int i = 0; i < 4; ++i) {
bytes[i] = SubBytes_single((word >> (8 * (3 - i))) & 0xFF);
}
unsigned int result = 0;
for (int i = 0; i < 4; ++i) {
result = (result << 8) | bytes[i];
}
return result;
}

// Helper function for converting byte array to state matrix (4x4)
void bytesToState(const unsigned char *in, unsigned char state[4][4]) {
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
state[j][i] = in[i * 4 + j];
}
}
}

// Helper function for converting state matrix to byte array
void stateToBytes(const unsigned char state[4][4], unsigned char *out) {
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
out[i * 4 + j] = state[j][i];
}
}
}

2.3 密钥扩展实现

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
// Key Expansion for AES-256
void KeyExpansion(const unsigned char *key, unsigned int *roundKeys) {
unsigned int temp;
unsigned int w[60]; // Word array to store expanded key. AES-256 requires 60 words (4 bytes each)

// 1. Copy the initial key into the first Nk words of w. Nk = key size in 32-bit words. For AES-256, Nk = 8 (256 bits / 32 bits/word).
for (int i = 0; i < 8; ++i) {
w[i] = (key[4 * i] << 24) | (key[4 * i + 1] << 16) | (key[4 * i + 2] << 8) | key[4 * i + 3];
}

// 2. Generate remaining words
for (int i = 8; i < 60; ++i) { // Generate up to Nr+1 round keys (Nr = 14 for AES-256, so we need 15 keys = 60 words)
temp = w[i - 1];
if (i % 8 == 0) { // For every Nk-th word (every 8th word for AES-256)
temp = subWord(rotWord(temp)) ^ Rcon[(i / 8) - 1]; // Apply RotWord, SubWord, and XOR with Rcon
} else if (i % 8 == 4) { // For AES-256 only, apply SubWord on every Nk/2-th word (every 4th word) starting from w[4] and onwards. Not needed for AES-128 or AES-192
temp = subWord(temp);
}
w[i] = w[i - 8] ^ temp; // XOR with the word 8 positions back
}

// 3. Copy expanded key words into roundKeys array (each round key is 128-bits = 4 words)
for (int i = 0; i < 15; ++i) { // 15 round keys (initial + 14 rounds)
roundKeys[i * 4 + 0] = w[i * 4];
roundKeys[i * 4 + 1] = w[i * 4 + 1];
roundKeys[i * 4 + 2] = w[i * 4 + 2];
roundKeys[i * 4 + 3] = w[i * 4 + 3];
}
}

3. 逆向

我们说了这么多原理,但是如何解决 AES 呢?

实际上,AES 仍是一个对称加密算法,因此只要获得密文和种子密钥就可以得到明文:

Example

GWCTF 2019 - re3
Self-Modified Code

1
2
3
4
5
6
7
8
from Crypto.Cipher import AES
from Crypto.Util.number import *

key = bytes.fromhex('cb8d493521b47a4cc1ae7e62229266ce')
cipher_text = bytes.fromhex('BC0AADC0147C5ECCE0B140BC9C51D52B46B2B9434DE5324BAD7FB4B39CDB4B5B')
plain_text = AES.new(key, AES.MODE_ECB).decrypt(cipher_text)
print(plain_text)
# flag{924a9ab2163d390410d0a1f670}

©2025-Present Watermelonabc | 萌 ICP 备 20251229 号

Powered by Hexo & Stellar 1.33.1 & Vercel & HUAWEI Cloud
您的访问数据将由 Vercel 和自托管的 Umami 进行隐私优先分析,以优化未来的访问体验

本博客总访问量:capoo-2

| 开往-友链接力 | 异次元之旅

中文独立博客列表 | 博客录 随机博客

AI 参与指数(IIIA)2 级

猫猫🐱 发表了 56 篇文章 · 总计 232.1k 字