1. 前言
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。
2. 原理
2.1 术语
- 分组(Block):在 AES 中,每个数据块的大小为 128 位(即 16 字节),无论密钥长度是多少。加密过程是基于每个数据块进行的。
- 密钥(Key):AES 使用对称加密,即加密和解密使用相同的密钥。AES 支持三种不同长度的密钥:128 位、192 位和 256 位。
- 轮数(Rounds):加密过程中一系列操作的执行周期。每轮包括若干个操作步骤,用于对数据进行变换。根据密钥的长度,AES 会执行不同数量的轮。
- 轮密钥(Round Key):从原始密钥(主密钥)通过密钥扩展算法生成的每一轮加密所需的密钥。在 AES 中,密钥扩展通过一系列的算法生成多个轮密钥,并且每轮加密时都会与相应的轮密钥进行混合。
- S-Box(Substitution Box):字节替代(Substitution)步骤使用的替代表。它是通过非线性变换来替代输入的字节值,增加加密的复杂度,从而增强安全性。S-Box 的设计是为了抵抗差分密码分析和线性密码分析。
- 加密模式(Mode of Operation):AES 加密算法用于处理多个数据块的方式。AES 不支持直接加密超过 128 位的数据,因此加密模式用于将多个数据块串联起来。常见的加密模式有**ECB(Electronic Codebook),CBC(Cipher Block Chaining),CFB(Cipher Feedback),OFB(Output Feedback),CTR(Counter),CTS(Cipher Text)**等。
- 初始化向量(IV, Initialization Vector):一种随机值,用于加密操作中,以确保相同的明文在多次加密时产生不同的密文。特别是在像 CBC(密文块链接)模式下,IV 是加密的必要参数。
- 填充(Padding):在加密过程中,如果明文的长度不是 128 位的整数倍时,需要填充额外的数据来使其长度对齐。常见的填充方法包括 PKCS7 填充。
2.2 加密
AES 是基于 分组密码 的设计,使用相同的密钥进行加密和解密。该算法具有128位的分组长度,支持128/192/256位的密钥长度。
AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“体(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。
加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:
- 轮密钥加
AddRoundKey
:矩阵中的每一个字节都与该次回合密钥做异或操作,轮密钥是通过密钥扩展从初始密钥中生成的。 - 字节替代
SubBytes
:透过一个非线性的替换函数,使用 S-Box(字节替换表)对状态矩阵中的每个字节进行替换。 - 行移位
ShiftRows
:将矩阵中的每个横列进行循环式移位。 - 列混淆
MixColumns
:为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns
步骤,而以另一个AddRoundKey
取代。
2.3 密钥扩展
密钥扩展是 AES 加密中非常重要的一步,其作用是根据原始的加密密钥生成多个轮密钥。原始的密钥会经过一系列的处理(例如,使用 轮常量 和 S-Box)生成轮密钥,供每一轮加密使用。
- AES-128 需要生成 11 个轮密钥(1 个初始密钥和 10 个轮密钥)。
- AES-192 需要生成 13 个轮密钥(1 个初始密钥和 12 个轮密钥)。
- AES-256 需要生成 15 个轮密钥(1 个初始密钥和 14 个轮密钥)。
2.4 解密
AES 的解密过程与加密过程类似,但操作顺序和某些步骤有所不同。解密过程通过逆操作来完成。
SubBytes
变为InvSubBytes
(逆字节替代)。ShiftRows
变为InvShiftRows
(逆行移位)。MixColumns
变为InvMixColumns
(逆列混合)。AddRoundKey
步骤与加密过程相同。
解密过程的关键在于使用了与加密相同的轮密钥,但执行顺序与加密过程相反。
2.5 安全性
AES 算法被认为是 高度安全 的。它抗拒常见的密码分析方法,如 已知明文攻击 和 选择明文攻击。AES 的安全性基于以下几点:
- 密钥长度越长,破解的难度越大。例如,AES-128 需要 2^128 次操作才能暴力破解,而 AES-256 需要 2^256 次操作,这几乎是不可能完成的任务。
- 采用了复杂的 非线性操作 和 分散技术,使得密文和明文之间的关系非常复杂,难以通过简单的模式识别来破解。
3. 步骤
3.1 密钥扩展(keyExpansion)
密钥扩展将固定N = (4 * Nk)字节的密钥扩展成(NR + 1)*16字节,密钥扩展简单流程如下:
1. 初始化
将原始的密钥(长度为 Nk
字)分成 4 字节一组存储在扩展密钥矩阵 W
中。每个字的大小为 4 字节(32 位)。(例如对于 AES-256,Nk = 8
)。
假设输入密钥为 K = [K0, K1, K2, ..., K(Nk-1)]
(每个 Ki
为 32 位的字),将其直接复制到扩展密钥的前 Nk
个 32 位字:
W[i]=K[i] 0≤i<Nk
2. **轮常量 Rcon
**
轮常量是与当前轮数相关的一个字节,它在 AES 密钥扩展过程中起到确保每个轮密钥不相同的作用。轮常量的生成规则如下:
Rcon[1] = 0x01
Rcon[i] = Rcon[i-1] * 2
(对于i > 1
)
因此,通常可以将轮常量定义为常量。
const byte Rcon[10] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36};
3. T函数
一个复杂函数,由三个步骤构成:字循环,字节代换和轮常量异或。
- 字循环(Word Rotation):将1个字中的4个字节循环左移1个字节
Rotate(W[i−1]) = (W[i−1][1],W[i−1][2],W[i−1][3],W[i−1][0])
- 字节代换(Byte Substitution):使用预定义的S盒进行替代每一个字节
Subword(W[i−1]) = SBox(W[i−1])
- 轮常量异或(XOR with Round Constant):对
T(W[i - 1])
的第一个字节进行轮常量异或,假设rconIndex
表示当前的轮常量索引,那么
T(W[i−1]) = SBox(W[i−1]) ⊕ Rcon[rcon_index]
4. 生成轮密钥
从 W[Nk]
开始,逐步计算生成剩余的轮密钥(直到 W[4*(NR+1) - 1]
)。
对于每个 W[i],计算如下:
-
对于
i % Nk == 0
的字,对前一个轮密钥进行T函数转换,与W[i - Nk]
进行异或。W[i] = W[i - Nk] ⊕ T(W[i - 1])
-
其他字节(
W[i+1]
,W[i+2]
,W[i+3]
)直接将与前一个轮密钥的对应字节进行异或操作:W[i] = W[i - Nk] ⊕ W[i - 1]
重复此过程,直到所有轮密钥都被生成。
void KeyExpansion(byte* key, byte* expanded, int Nk, int Nr) {
int i = 0;
// initialize the first Nk*4 bytes of the expanded key
while (i < Nk << 2) {
expanded[i] = key[i];
expanded[i + 1] = key[i + 1];
expanded[i + 2] = key[i + 2];
expanded[i + 3] = key[i + 3];
i += 4;
}
int rcon_index = 0;
byte temp[4];
while (i < (Nr + 1) << 4) {
// copy the W[k-1] to temp
for (int j = 0; j < 4; j++)
temp[j] = expanded[i - 4 + j];
if ((i % (Nk << 2)) == 0) {
// word rotation
// subword
// xor with round constant
k = temp[0];
temp[0] = s_box[temp[1]] ^ r_con[rcon_index++];
temp[1] = s_box[temp[2]];
temp[2] = s_box[temp[3]];
temp[3] = s_box[k];
}
// additional subword processing for aes256
else if (Nk == 8 && ((i & 0x1F) ^ 0x10) == 0) {
for (int j = 0; j < 4; j++) temp[j] = s_box[temp[j]];
}
// W[i] = W[i - Nk] ^ W[i - 1]
for (int j = 0; j < 4; j++)
expanded[i + j] = expanded[i - (Nk << 2) + j] ^ temp[j];
i += 4;
}
}
3.2 轮密钥加(AddRoundKey)
轮密钥加是将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作,增加加密过程的复杂度和安全性。
void AddRoundKey(byte* state, byte* round_key) {
for (int i = 0; i < 16; i++)
state[i] ^= round_key[i];
}
3.3 字节替代(SubBytes)
通过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节,使得加密过程更加复杂,增加安全性。加密使用S-box(Substitution box),解密使用inverse S-box。
void SubBytes(byte* state) {
for (int i = 0; i < 16; i++)
state[i] = sbox[state[i]];
}
3.4 行移位(ShiftRows)
将矩阵中的每个横列进行循环式移位,加密和解密的移动方向相反。
void ShiftRows(byte* state) {
byte temp;
// The first row remains unchanged.
// Rotate first row 1 columns to left
temp = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = temp;
// Rotate second row 2 columns to left
temp = state[2];
state[2] = state[10];
state[10] = temp;
temp = state[6];
state[6] = state[14];
state[14] = temp;
// Rotate third row 3 columns to left
temp = state[15];
state[15] = state[11];
state[11] = state[7];
state[7] = state[3];
state[3] = temp;
}
3.5 列混淆(MixColumns)
列混合是一个替代操作,是AES算法中最复杂的一步,只在第0到r-1轮中用到,最后一轮不使用该变换。
1. 有限域GF(2^8)运算
在 GF(2^8) 中,运算(如加法、乘法)都在二进制数上进行,并且每个运算都使用模一个不可约多项式。AES使用的是:
m(x) = x^8 + x^4 + x^3 + x + 1
- 加法:按位异或(XOR)操作。对于两个字节
a
和b
,它们的和a + b
是它们的按位异或:
a + b = a ^ b
- 乘法:基于模不可约多项式的。对于两个字节
0xb
和0x5
,它们的乘积0xb * 0x5
需要按以下步骤进行:
1)执行常规的多项式乘法
0xb * 0X5 = (x^3 + x^1 + x^0) * (x^2 + x^0)
= (x^5 + x^3 + x^2) + (x^3 + x^1 + x^0)
= x^5 + x^3*(1^1) + x^2 + x^1 + x^0
= x^5 + x^2 + x^1 + x^0
= 0x27
2)对多项式 x^8 + x^4 + x^3 + x + 1取模
0x27 % (x^8 + x^4 + x^3 + x + 1) = 0x27 ^ 0x1B = 0x27
编程实现:
byte gmul(byte a, byte b) {
byte p = 0;
byte hi_bit_set;
for (int i = 0; i < 8 && b; i++) {
if (b & 1) p ^= a; // xor if the lowest bit of b is 1
hi_bit_set = (a & 0x80); // if the highest bit of a is set
a <<= 1; // Multiply a by x (shift left by 1)
if (hi_bit_set) a ^= 0x1B; // if the highest bit was set, reduce modulo the AES polynomial (0x1b)
b >>= 1; // // divide b by x (shift right by 1)
}
return p;
}
2.列混淆
通过将state矩阵与常矩阵C相乘以达到在列上的扩散,实质是在有限域GF(256)上的多项式乘法运算。
// 加密
void MixColumns(byte* state) {
byte temp[4];
for (int i = 0; i < round_key_size; i += 4) {
temp[0] = gmul(state[i], 2) ^ gmul(state[1 + i], 3) ^ state[2 + i] ^ state[3 + i];
temp[1] = state[i] ^ gmul(state[1 + i], 2) ^ gmul(state[2 + i], 3) ^ state[3 + i];
temp[2] = state[i] ^ state[1 + i] ^ gmul(state[2 + i], 2) ^ gmul(state[3 + i], 3);
temp[3] = gmul(state[i], 3) ^ state[1 + i] ^ state[2 + i] ^ gmul(state[3 + i], 2);
for (int j = 0; j < 4; j++)
state[i + j] = temp[j];
}
}
// 解密
void InvMixColumns(byte* state) {
byte temp[4];
for (int i = 0; i < round_key_size; i += 4) {
temp[0] = gmul(state[i], 14) ^ gmul(state[1 + i], 11) ^ gmul(state[2 + i], 13) ^ gmul(state[3 + i], 9);
temp[1] = gmul(state[i], 9) ^ gmul(state[1 + i], 14) ^ gmul(state[2 + i], 11) ^ gmul(state[3 + i], 13);
temp[2] = gmul(state[i], 13) ^ gmul(state[1 + i], 9) ^ gmul(state[2 + i], 14) ^ gmul(state[3 + i], 11);
temp[3] = gmul(state[i], 11) ^ gmul(state[1 + i], 13) ^ gmul(state[2 + i], 9) ^ gmul(state[3 + i], 14);
for (int j = 0; j < 4; j++)
state[i + j] = temp[j];
}
}
3.6 加解密
// 加密
void AES_encrypt(const byte* input, byte* output, const byte* key, int key_size) {
const int Nk = key_size / 32;
const int Nr = (Nk == 4) ? 10 : (Nk == 8 ? 14 : 12);
byte expanded_keys[(14 + 1) * 16];
KeyExpansion(key, expanded_keys, Nk, Nr);
memcpy(output, input, 16);
AddRoundKey(output, expanded_keys);
for (int round = 1; round < Nr; round++) {
SubBytes(output);
ShiftRows(output);
MixColumns(output);
AddRoundKey(output, expanded_keys + round * 16);
}
SubBytes(output);
ShiftRows(output);
AddRoundKey(output, expanded_keys + Nr * 16);
}
// 解密
void AESDecrypt(const byte* input, byte* output, const byte* key, int key_size) {
const int Nk = key_size / 32;
const int Nr = (Nk == 4) ? 10 : (Nk == 8 ? 14 : 12);
byte expanded_keys[(14 + 1) * 16];
KeyExpansion(key, expanded_keys, Nk, Nr);
memcpy(output, input, 16);
AddRoundKey(output, expanded_keys + Nr * 16);
for (int round = Nr - 1; round > 0; round--) {
InvShiftRows(output);
InvSubBytes(output);
AddRoundKey(output, expanded_keys + round * 16);
InvMixColumns(output);
}
InvShiftRows(output);
InvSubBytes(output);
AddRoundKey(output, expanded_keys);
}