最近看到了一个比较浅显易懂的 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 - CTF Wiki)
一般来讲,我们会使用十六进制对明文进行编码。
AES 每轮的加密主要分为 4 个操作:
- 字节替换 (SubBytes)
- 行移位 (ShiftRows)
- 列混淆 (MixColumns)
- 轮密钥加 (AddRoundKey)
其中,进行第一轮加密前,需要进行一次 “轮密钥加” 以生成第一轮加密所需的输入;最后一轮仅不执行 “列混淆” 操作。

(来源:[原创] 128 位 AES 加密算法图解 - 看雪,注意上面的列混淆的矩阵乘法等号左边的列向量应该在右边。)
1.0 种子密钥加
其实是就是轮密钥加,可以往后面看看。这里是把明文矩阵和种子密钥异或得到第一轮的状态矩阵。
1.1 字节替换
简单说就是一个映射操作,以防止出现重复元素。

如上图,a[0,0]
是 0x12
, 查 S 盒 [1,2]
处得到 0xC9
;a[0,1]
是 0xAB
,查 S 盒 [A,B]
处,也就是 [10,11]
,得到 0x62
。
如果是逆 S 盒,那方法也相同,以 0xC9
为例,查逆 S 盒 [C,9]
即 [13,9]
,对应表中值为 0x12
。
1.2 行移位
行位移将输入的数据作为一个 4x4 矩阵进行处理,可以理解为左循环位移操作。
正向行位移,第一行保持不变,第二行左移 1 字节 ,第三行左移 2 字节,第四行左移 3 字节

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

1.3 列混淆
列混合变换就是矩阵相乘,行位移后的矩阵和修补矩阵 (fixed matrix) 做矩阵相乘,得出输出列。修补矩阵是固定的。
解密时,逆向列混淆的修补矩阵与正向列混淆的修补矩阵互为逆矩阵。
1.4 轮密钥加
根据传入的轮数,将本轮列混淆后的加密结果与对应的轮密钥 k
异或。

在 AES (step-by-step) 可以看到每一轮加密的示例过程。
1.5 轮密钥生成
每轮的轮密钥是以种子密钥为基础生成的,称为密钥扩展 (KeyExpansion)。密钥扩展按列计算,每四列构成一个轮密钥。进行 轮加密就需要 个轮密钥,最终得到 列密钥(因为包含了种子密钥)
令种子密钥的第一列为第 0 列,按升序排序。
如果列数不是 4 的倍数,那么计算公式为:
如果列数是 4 的倍数,计算公式会复杂一点:
首先,将 的最上方的元素放到该列最后的元素之后,使用 S 盒进行字节替换,得到 。
然后按照下面的公式计算列:
其中 是给定的轮常量。
2. C++ 实现 (AES-256)
2.1 常量定义
首先,定义必要的常量和映射表。
1 |
|
2.2 操作辅助函数
1 |
|
2.3 密钥扩展实现
1 | // Key Expansion for AES-256 |
3. 逆向
我们说了这么多原理,但是如何解决 AES 呢?
实际上,AES 仍是一个对称加密算法,因此只要获得密文和种子密钥就可以得到明文:
GWCTF 2019 - re3
Self-Modified Code
1 | from Crypto.Cipher import AES |