AES算法简介
目录
- AES算法描述
- 数学基础
- 设计思想
- 基本变换
- 密钥扩展
- 加密过程
- 解密过程
- 常见攻击手段
一、AES算法概述
1.发展背景
1997年4月15日,美国ANSI发起征集AES (Advanced Encryption Standard) 的活动,并为此成立了AES工作小组。此次活动的目的是确定一个非保密的、可以公开技术细节的、全球免费使用的分组密码算法,以作为新的数据加密标准。
对AES的基本要求是:比三重DES快,至少与三重DES一样安全,数据分组长度128bit,密钥长度为128/192/256bit,可应用于公共领域并免费提供,应至少在30年内是安全的。
2000年10月,Rijndael算法凭借其安全性、高效率、可实现和使用灵活等优点成为美国新的高级加密标准(AES)
Rijndael算法的原型是Square算法,它的设计策略是宽轨迹策略。优点是能很好地评估算法抵抗差分和线性密码攻击的能力。
2.整体认识
AES算法选定的Rijndael算法中,分组长度固定为 128bit(16byte)。而AES-128、AES-192、AES-256 中的数字表示的是密钥长度,不是分组长度。
| 算法 | 密钥长度 Nk(32-bit 字) | 轮数 Nr | 分组长度 |
|---|---|---|---|
| AES-128 | 4 | 10 | 128 bit |
| AES-192 | 6 | 12 | 128 bit |
| AES-256 | 8 | 14 | 128 bit |
AES的基本运算单位是字节(Byte),加密和解密过程都是在一个4X4的字节矩阵上运作,这个矩阵又称为 “体(state)” 或者 “状态” 。字节矩阵初始值是一个明文块。
AES 是一个 “代换-置换网络”(Substitution-Permutation Network,SPN)。加密过程执行多轮以下操作:
- 字节代替
- 行移位
- 列混合
- 轮密钥加
最后一个加密循环中省略列混合步骤。
解密过程除了轮密钥加外,其它都是加密过程的逆运算:
- 逆行移位
- 逆字节代替
- 轮密钥加
- 逆列混合
可以看到解密和加密的运算过程不是完全颠倒。
二、数学基础
1.$Byte$ 的多项式表示
AES算法将 $b_{7}b_{6}b_{5}b_{4}b_{3}b_{2}b_{1}b_{0}$ 构成的字节 b 看成系数属于 GF(2)、次数小于 8 的多项式: \(b_7x^7+b_6x^6+\cdots+b_1x+b_0\)
例如:
1
0x57 = 01010111₂
所以: \(\texttt{0x57}=x^6+x^4+x^2+x+1\)
每个 bit 就是相应次数项的系数。
这样一来,两个字节相加就是多项式系数异或:
1
2
3
0x57 + 0x83
= 0x57 XOR 0x83
= 0xd4
2.有限域 $GF(2^8)$
AES定义的字节域为:
\[GF(2^8)=GF(2)[x]/(m(x))\]其中 $m(x)=x^8+x^4+x^3+x+1$ ,二进制表示为
1
1 0001 1011₂ = 0x11b
可见有限域$GF(2^8)$上的乘法,就是两个多项式正常相乘,系数模2,最后模多项式 $m(x)$
因为 $m(x)$ 不可约,所以所有非零元素都存在乘法逆元。
易知以上乘法的单位元为0x01 ,可通过扩展欧几里得算法求得乘法逆元:
xtime
在有限域 $GF(2^8)$上还定义了一个运算,称为x乘法,其定义为: \(x\cdot b(x)=b_{7}x^8+b_{6}x^7+b_{5}x^6+b_{4}x^5+b_{3}x^4+b_{2}x^3+b_{1}x^2+b_{0}x \ \ mod \ \ m(x)\) 简化流程如下:
- 如果$b_{7}=0$ ,直接在字节内左移一位(最后一位补0)
- 如果$b_{7}=1$ ,还要再与
0x1b异或
这里使用 0x1b 而不是 0x11b,是因为左移产生的 $x^8$ 项已经被消去,只需异或模多项式的低 8 bit。
x的幂乘运算可以重复应用xtime来实现(快速幂)
例如,计算 $57\cdot 13$ : \(\begin{align} &57\cdot02=xtime(57)=AE \\ &57\cdot04=xtime(AE)=47 \\ &57\cdot08=xtime(47)=8E \\ &57\cdot10=xtime(8E)=07 \\ &57\cdot13=57\cdot(01\oplus 02\oplus 10) \\ &=57\oplus AE\oplus 07=FE \end{align}\)
3.系数在 $GF(2^8)$ 上的多项式
4个字节的向量可以表示为系数在$GF(2^8)$ 上的次数小于4的多项式。
加法
多项式的加法就是对应系数相加。换句话说就是4字节的逐比特异或、
乘法
取模 $M(x)=x^4+1$ ,这样使得次数小于4的多项式的乘积次数仍然小于4。
设 $a(x)=a_{3}x^3+a_{2}x^2+a_{1}x+a_{0}$,$b(x)=b_{3}x^3+b_{2}x^2+b_{1}x+b_{0}$,则:
\[c(x)=a(x)\otimes b(x)=c_{3}x^3+c_{2}x^2+c_{1}x+c_{0}\]由于 $x^j \ \ mod(x^4+1)=\ \ x^{j \ \ mod \ \ 4}$ ,所以:
\[\begin{pmatrix} c_{0} \\ c_{1} \\ c_{2} \\ c_{3} \\ \end{pmatrix}= \begin{pmatrix} a_{0} & a_{3} & a_{2} & a_{1} \\ a_{1} & a_{0} & a_{3} & a_{2} \\ a_{2} & a_{1} & a_{0} & a_{3} \\ a_{3} & a_{2} & a_{1} & a_{0} \\ \end{pmatrix} \begin{pmatrix} b_{0} \\ b_{1} \\ b_{2} \\ b_{3} \\ \end{pmatrix}\]显然只要矩阵
\[\begin{pmatrix} a_{0} & a_{3} & a_{2} & a_{1} \\ a_{1} & a_{0} & a_{3} & a_{2} \\ a_{2} & a_{1} & a_{0} & a_{3} \\ a_{3} & a_{2} & a_{1} & a_{0} \\ \end{pmatrix}\]可逆,多项式$a(x)=a_{3}x^3+a_{2}x^2+a_{1}x+a_{0}$ 就是模 $x^4+1$ 可逆的。
容易发现,x模乘多项式相当于字节循环移位。
三、设计思想
AES 的设计思想可以概括为:
用少量结构简单、可逆且高效的基本变换,通过多轮“非线性混淆 + 线性扩散 + 密钥注入”,让明文、密钥和密文之间形成复杂关系。
AES的设计力求满足以下3条标准:
- 抵抗所有已知攻击
- 在多个平台上速度快,编码紧凑
- 设计简单
宽轨迹策略
AES 设计中非常重要的思想是 Wide Trail Strategy(宽轨迹策略)。
它的核心是:
- S-box 提供局部非线性;
- ShiftRows 把字节跨列移动;
- MixColumns 让一列中的字节互相影响;
- 多轮组合后,差分路径或线性路径必须经过大量活跃 S-box。
“活跃 S-box” 指输入和输出之间存在非零差分或非平凡线性关系的 S-box。
攻击者想利用差分或线性关系,就必须同时处理很多 S-box,而每个 S-box 的最大差分概率、最大线性偏差都受到限制。因此整体攻击复杂度会快速上升。
非线性层
AES采用精妙数学设计的S盒:求逆元+仿射变换。这让它的安全性被很好的确定,比如差分均匀性、非线性度等。它有如下优点:
- 非线性度高;
- 差分均匀性好;
- 不容易被线性密码分析和差分密码分析利用;
- 没有明显的固定点和简单对称结构;
- 映射是双射,保证可逆。
线性层
ShiftRows 和 MixColumns操作都属于线性层,但它们的分工不同
ShiftRows负责重新排列位置,它不改变字节值,只改变字节所在的列。
MixColumns负责在每列内做有限域矩阵乘法,它会把一个字节的影响扩散到同一列的多个字节。
如果只有 MixColumns 而没有 ShiftRows,每一列之间联系不足;如果只有 ShiftRows 而没有 MixColumns,只是位置重排,无法充分混合字节值。两者配合才能快速形成全局扩散。
密钥扩展的设计思想
AES 不在每轮重复使用原始密钥,而是生成不同的轮密钥。
密钥扩展同时使用:
- RotWord:改变字节位置;
- SubWord:引入 S-box 非线性;
- Rcon:破坏轮之间的对称性;
- 异或递推:高效地产生新字。
设计目标是避免:
- 轮密钥之间过于相似;
- 某些密钥字节不影响后续轮;
- 出现明显的重复周期;
- 相关密钥攻击过于容易。
密钥扩展的目的是原始密钥的每个 bit 尽快影响所有轮密钥。
四、基本变换
AES算法中,加密和解密操作都是在一个4X4的字节矩阵上运作,这个矩阵称为状态(State)。状态用以字节为基本构成元素的矩阵阵列来表示,该阵列每列4byte数据,即32bit,因而明文列数为分组长度除以32,通常记为$N_{b}$。 \(N_{b}=\frac{分组长度(bit)}{32}\) 一般的,Rijndael算法列数$N_{b}$ 可以取的值为4、6、8,对应的明文分组长度为128、192、256bit。而AES算法的分组长度固定为128bit,因此AES明文列数等于固定值$N_{b}=4$。
初始明文矩阵、种子密钥和轮数
AES算法中初始状态矩阵由1组长度为 128bit 明文分组构成,以字节为单位,则总共有16byte,记为$B_{0},B_{1},B_{2},\dots,B_{14},B_{15}$。加密结束时,输出密文128bit也是以列方法按相同的顺序提取。
种子密钥类似地用一个以字节为元素的矩阵阵列表示,该阵列有4行,列数记为 $N_{k}$ \(N_{k}=\frac{密钥长度(bit)}{32}\) AES算法的密钥长度的列数为128bit、192bit、256 bit三种,因此不同密钥长度 $N_{k}$可以取的值分别为4、6、8。
迭代的轮数记为$N_{r}$ ,$N_{r}$ 与$N_{b}$ 和$N_{k}$ 有关,下表给出了其关系:
| $N_{r}$ | $N_{b}=4$ | $N_{b}=6$ | $N_{b}=8$ | | ——— | ——— | ——— | ——— | | $N_{k}=4$ | 10 | 12 | 14 | | $N_{k}=6$ | 12 | 12 | 14 | | $N_{k}=8$ | 14 | 14 | 14 | 从表中可以看出,AES的迭代轮数为10、12或14
Rijndael的轮函数由4个不同的计算部件组成,分别是字节代换(ByteSub),行移位(ShiftRow),列混合(MixColumn)和密钥加(AddRoundKey)
字节代换(ByteSub)
字节代换是非线性变换。代换表,也就是S盒,有两个设计原则:
- S盒是可逆的
- 不能存在这种情况:第 i 行第 j 列位置上的值为 ij
S盒由以下两个变换合成得到:
- 在有限域 $GF(2^8)$ 上求字节的乘法逆元,00的逆就是自身
- 对字节的逆元做如下的可逆变换:
S盒变换也可以通过查 16X16 的表来进行,映射原则:输入字节的前4bit指定S 盒的行值,后4bit指定S盒的列值,行和列所确定S盒位置的元素作为输出。
ByteSub的逆变换可通过以下两步实现:首先进行仿射变换的逆变换,再求在$GF(2^8)$ 上的逆元
行移位(ShiftRow)
在AES算法中,行移位作用于S盒的输出。其中,状态矩阵的4个行循环以字节为基本单位进行左移。
第一行循环左移0字节,第二行循环左移1字节,第三行循环左移2字节,第四行循环左移3字节。这可以使列得到完全的重排。
列混合(MixColumn)
列混合(MixColumn)操作是将状态矩阵的每一列视为 $GF(2^8)$ 上的多项式,再分别与多项式 $a(x)$ 进行模 $x^4+1$ 的乘法。其中 $a(x)$ 可逆: \(a(x)=03x^3+01x^2+01x+02\) 列混合操作可用以下的公式表示: \(c(x)=a(x)\otimes b(x)=a(x) \times b(x) \ \ mod (x^4+1)\) 也可以用矩阵式表示:
\[\begin{pmatrix} c_{0} \\ c_{1} \\ c_{2} \\ c_{3} \\ \end{pmatrix} = \begin{pmatrix} 02 & 03 & 01 & 01 \\ 01 & 02 & 03 & 01 \\ 01 & 01 & 02 & 03 \\ 03 & 01 & 01 & 02 \\ \end{pmatrix} \begin{pmatrix} b_{0} \\ b_{1} \\ b_{2} \\ b_{3} \\ \end{pmatrix}\]轮密钥加(AddRoundKey)
最后一项基本操作是轮密钥加,是将列混合的输出状态矩阵与子密钥状态进行异或运算(子密钥是从初始密钥派生而来的)。子密钥状态矩阵长度等于明文状态矩阵长度。
五、密钥扩展
密钥扩展是从种子密钥获得轮密钥的过程,有以下基本原则:
- 轮密钥比特数=分组长度 $\times$ (轮数 $+1$ )
- 种子密钥被扩展成扩展密钥
- 轮密钥从扩展密钥中取,每轮取 $N_{b}$ 个字(32bit)
符号说明
$W[\ \ ]$ : 扩展密钥是以字(32bit)为元素的一维数组,表示为 $W[ \ \ N_{b}\cdot (N_{r}+1)\ \ ]$ ,其中,前 $N_{k}$ 个字取为种子密钥。
$key[\ \ ]$ : $key[\ \ 4\cdot N_{k}\ \ ]$ 为种子密钥,看作以字节为元素的一维数组
$SubByte$ : 字节代换
$RotByte$ : 以字节为单位的循环左移位,输入输出都是字
$Rcon[i]$ : 轮常数,为一个字。定义为: \(Rcon[i]=(RC[i],00,00,00)\) 其中, $RC[i]$ 是 $GF(2^8)$ 中值为 $x^{i-1}$ 的元素,即有限域 $GF(2^8)$ 上的x乘
根据RC计算方法,前10轮$RC[i]$ 的值如下表所示:
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| $RC[i]$ | 01 | 02 | 04 | 08 | 10 | 20 | 40 | 80 | 1b | 36 |
算法伪代码
$N_{k}\leq6$
1
2
3
4
5
6
7
8
9
10
11
12
KeyExpansion(byte key[4*N_k],W[N_b*(N_r+1)]){
int i;
for(i=0;i<N_k;i++){
W[i]=(key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]);
}
for(i=N_k;i<N_b*(N_r+1);i++){
temp=W[i-1];
if(i%N_k==0)
temp=SubByte(RotByte(temp))^Rcon[i/N_k];
W[i]=W[i-N_k]^temp;
}
}
可以看出,扩展密钥的前 $N_{k}$ 个字即为种子密钥,之后的每个字 $W[i]$ 等于前一个字$W[i-1]$ 与 前$N_{k}$ 个位置的字 $W[i-N_{k}]$ 的异或;不过当 $N_{k} \mid i$ 时,需要先将前一个字$W[i-1]$ 作如下处理:
1字节的循环左移位RotByte->用S盒进行变换SubByte->与轮常数$Rcon[i/N_{k}]$ 进行异或
下图展示了 AES-128 的密钥扩展过程
$N_{k}>6$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
KeyExpansion(byte key[4*N_k],W[N_b*(N_r+1)]){
int i;
for(i=0;i<N_k;i++){
W[i]=(key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]);
}
for(i=N_k;i<N_b*(N_r+1);i++){
temp=W[i-1];
if(i%N_k==0)
temp=SubByte(RotByte(temp))^Rcon[i/N_k];
else if(i%N_k==4)
temp=SubByte(temp);
W[i]=W[i-N_k]^temp;
}
}
$N_{k}>6$ 与$N_{k}\leq6$ 的密钥扩展算法的区别在于,除了$N_{k} \mid i$ 要进行特殊处理,在 $i\ \ mod \ \ N_{k }=4$ 时,也要对前一个字$W[i-1]$ 经过SubByte变换
轮密钥选取
轮密钥 i 由扩展密钥 $W[N_{b}\cdot i]$ 到 $W[N_{b}\cdot (i+1)-1$ 给出:
六、加密过程
AES-128 使用 16 byte 明文块和 16 byte 密钥,共 10 轮:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输入明文
│
▼
AddRoundKey
│
▼
┌──────────────────┐
│ SubBytes │
│ ShiftRows │ 第 1~9 轮
│ MixColumns │
│ AddRoundKey │
└──────────────────┘
│
▼
SubBytes
ShiftRows 第 10 轮:没有列混合
AddRoundKey
│
▼
输出密文
可写成伪代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
state = plaintext
KeyExpansion(key,ExpandedKey)
state = AddRoundKey(state, ExpandedKey)
int i;
for(i=1;i<N_r;i++):
state = SubBytes(state)
state = ShiftRows(state)
state = MixColumns(state)
state = AddRoundKey(state, ExpandedKey+N_b*i)
state = SubBytes(state)
state = ShiftRows(state)
state = AddRoundKey(state, ExpandedKey+N_b*N_r)
七、解密过程
AES密码算法采用的是S-P网络结构,加密过程与解密过程不同,解密过程是加密过程的逆过程。
AES解密过程中,轮密钥的次序与加密过程正好相反,解密过程输入密文,先做一次轮密钥加,然后进行r轮变换。前 r-1轮中,每轮基本操作为逆行移位、逆字节代替、轮密钥加和逆列混合,而最后一轮操作为逆行移位、逆字节代替和轮密钥加(没有逆列混合)。
逆字节代替(InvSubBytes)
逆S盒:
S盒对应的运算:
1.逆仿射变换:
\[\begin{pmatrix} b_0 \\ b_1 \\ b_2 \\ b_3 \\ b_4 \\ b_5 \\ b_6 \\ b_7 \end{pmatrix} = \begin{pmatrix} 0&0&1&0&0&1&0&1\\ 1&0&0&1&0&0&1&0\\ 0&1&0&0&1&0&0&1\\ 1&0&1&0&0&1&0&0\\ 0&1&0&1&0&0&1&0\\ 0&0&1&0&1&0&0&1\\ 1&0&0&1&0&1&0&0\\ 0&1&0&0&1&0&1&0 \end{pmatrix} \cdot \begin{pmatrix} b'_0 \\ b'_1 \\ b'_2 \\ b'_3 \\ b'_4 \\ b'_5 \\ b'_6 \\ b'_7 \end{pmatrix} + \begin{pmatrix} 1 \\ 0 \\ 1 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \end{pmatrix}\]2.在$GF(2^8)$ 上求逆元
逆行移位
与行移位相反,逆行移位循环右移。
若AES算法中 $N_{b}$ 为4,即第1行循环右移0字节,第2行循环右移1字节,第3行循环右移2字节,第4行循环右移3字节。
逆列混合
逆列混合操作与列混合操作一样,只是选择相乘的固定多项式不同,逆列混合与固定多项式 $d(x)=0B x^3+0Dx^2+09x+0E$ 相乘,然后模 $x^4+1$ 。同理可以用矩阵乘法表示:
\[\begin{pmatrix} b_0 \\ b_1 \\ b_2 \\ b_3 \end{pmatrix} = \begin{pmatrix} \mathrm{0E} & \mathrm{0B} & \mathrm{0D} & \mathrm{09}\\ \mathrm{09} & \mathrm{0E} & \mathrm{0B} & \mathrm{0D}\\ \mathrm{0D} & \mathrm{09} & \mathrm{0E} & \mathrm{0B}\\ \mathrm{0B} & \mathrm{0D} & \mathrm{09} & \mathrm{0E} \end{pmatrix} \cdot \begin{pmatrix} c_0 \\ c_1 \\ c_2 \\ c_3 \end{pmatrix}\]八、常见攻击手段
AES 只负责构造一个安全的分组密码,不负责解决所有密码学问题。它本身不负责:
- 密钥交换;
- 用户身份认证;
- 密码存储;
- 防止重放;
- 处理任意长度消息;
- 提供完整性认证;
- 解决密钥生命周期管理。
CTF 中的 AES 题目通常不是要求你破解 AES 算法本身,而是考察上述这些是否正确实现。
完整、正确实现的 AES-128/192/256 一般不会被直接暴力破解
1.Square Attack
积分攻击通过构造 256 个特殊明文,让 AES 内部的某些字节在多轮传播后满足 XOR 平衡条件,再通过猜测最后一轮密钥并逆向验证,筛选出正确密钥。它利用的是 AES 结构的可预测整体性质,主要针对减少轮数或错误实现,而不是直接击破标准 AES。
(挖个坑,找时间深入学一下这个攻击0ctf 2016 - people square)
2. ECB分组模式
ECB 没有随机化,也没有块之间的关联,因此会泄露明文结构。
典型特征
- 加密接口固定使用同一把密钥;
- 输入可以由选手控制;
- 密文长度按 16 byte 分块;
- 相同明文块产生相同密文块。
3.CBC模式相关攻击
- 字节反转攻击
- Padding Oracle
- IV 使用错误
详细参考分组模式的常见攻击方法。
4.Nonce 重用攻击
CTR 模式把 AES 作为密钥流生成器: \(C_i=P_i\oplus KS_i\) 其中: \(KS_i=AES_K(nonce\parallel counter_i)\) 如果同一密钥下重复使用 nonce 和计数器,两个消息会使用相同密钥流: \(C_1=P_1\oplus KS\) \(C_2=P_2\oplus KS\) 异或后: \(C_1\oplus C_2=P_1\oplus P_2\) 密钥流被抵消。
5.弱密钥派生
很多题目表面上使用 AES,真正的弱点却是使用弱密钥:
- 密码是短字符串;
- 密钥来自用户名、时间戳、生日等可猜信息;
- 使用 MD5/SHA-1 一次哈希代替密码派生函数;
- salt 固定或没有 salt;
- hex 字符串和原始 byte 混淆;
- 不同用户或不同文件共用相同密钥。
6.把 AES 错当作哈希
CTF 中可能看到错误设计:
1
hash(message) = AES_K(message) 的最后一块
这类设计可能导致:
- 可构造碰撞;
- 长度扩展问题;
- 块重排攻击;
- 截断或拼接攻击;
- 认证范围不完整。









