前言
本菜鸡最近在实际开发项目中,遇到了AES算法,从客户的需求来看主要用于方向盘锁和发动机防盗锁,运用了如下一些加密流程,其中涉及到AES算法,我就饶有兴致的学习了一下,也看了网上的大部分教程,总结了一些自己学习过程中的遇到的重难点,同时也写了一份代码进行加密计算。因为个人觉得计算量太大了,要重复多次,并没有什么必要,完全可以学会一遍流程之后,读懂加密步骤就行了,不必细算具体数值。(本文图片大部分来源于网络)
一、AES算法简介
AES(高级加密标准,Advanced Encryption Standard)是一种对称分组加密算法,由美国国家标准与技术研究院(NIST)于2001年正式发布,用于替代安全性不足的DES算法。其设计基于比利时密码学家Joan Daemen和Vincent Rijmen提出的Rijndael算法,核心目标是为电子数据提供高效且高强度的加密保护,例如目前我遇到的汽车电子开发项目中就有它的身影。
二、算法流程
首先是初始密钥相加,之后进行9轮的主轮循环,每轮执行四个步骤:(1) 字节替代(SubBytes)
使用**S盒(Substitution Box)**对State矩阵中的每个字节进行非线性替换,提供混淆效果。
(2) 行移位(ShiftRows)
对State矩阵的每一行进循环左移:
第0行不移位,第1行左移1字节,第2行左移2字节,第3行左移3字节。
(3) 列混淆(MixColumns)
对State矩阵的每一列进行线性变换(通过固定矩阵乘法),增强扩散性。
(4) 轮密钥加(AddRoundKey)
将当前State矩阵与当前轮子密钥按字节进行异或操作。
最后第十轮 最终轮(第10轮,无列混淆)
执行SubBytes → ShiftRows → AddRoundKey(跳过MixColumns)。
具体流程图:
1.初始轮密钥加(AddRoundKey)
当我们获得16字节明文和16字节密钥之后,将他们排列成4x4矩阵。如下图所示:
明文:
密钥:
然后把这两个矩阵进行相异或操作,如下图所示:
这样就完成了初始轮密钥加(AddRoundKey)步骤。
这里我当时为了能够手推计算加密过程,就写了个异或操作的代码文件,方便用来计算。
int main() {
unsigned int hex1, hex2, result;
char choice;
do {
// 输入第一个十六进制数(可带0x前缀)
printf("请输入第一个十六进制数:");
scanf("%x", &hex1);
// 输入第二个十六进制数(可带0x前缀)
printf("请输入第二个十六进制数:");
scanf("%x", &hex2);
// 执行异或运算
result = hex1 ^ hex2;
// 输出结果(固定两位大写格式)
printf("0x%02X ^ 0x%02X = 0x%02X\n", hex1, hex2, result);
// 询问是否继续
printf("继续输入吗?(Y/N): ");
getchar(); // 吸收输入缓冲区残留的换行符
choice = getchar();
} while (toupper(choice) == 'Y'); // 输入Y/y继续循环
return 0;
}
2.字节替代
字节代替的主要功能是通过S盒完成一个字节到另外一个字节的映射。(就是查表,把第一步生成的4x4矩阵在S盒查表得到。S盒的详细构造方法可以参考文献:Joan Daemen and Vincent Rijmen, The Design of Rijndael, AES - The Advanced Encryption Standard, Springer-Verlag 2002 (238 pp.))这里直接给出构造好的结果,下图(a)为S盒,图(b)为S-1(S盒的逆)。S盒用于提供密码算法的混淆性。
把第一步轮密钥加后产生的每一个字节用十六进制表示
然后以十六进制的第一个数字为行,第二个数字为列,在S盒表中查找对应的数字,用这个数字来代替原先的数字,这样就完成了字节变换。
例如:这个是第一步生成的
{EA 04 65 85
83 45 5D 96
5C 33 98 B0
F0 2D AD C5},
根据查表后的结果为:
3.行移位
规则如下:
第一行保持不变,第二行循环左移8bit,第三行循环左移16bit,第四行循环左移24bit
如下图所示:
4.列混淆
将步骤3得到的矩阵进行左乘固定矩阵,就会得到新的矩阵,这里的固定矩阵应该是AES标准规定的,如下图所示:
需要注意的是这里的乘法不是10进制的乘法,具体计算步骤如下:
主要有三种情况:
假设矩阵某个数为x
1)那么01时:
x01,即为x本身;
2)乘02时:
x02,需要将x的二进制左移一位,右边补0,接下来判断是否溢出(即x的二进制最高位为1时),那么就需要再异或上1B;
举例:30 * 02
30的二进制为0011 0000,左移一位得到:0110 0000,所以结果为60.
举例:02 * 87
87的二进制位 1000 0111,左移一位得到 0000 1110,因为本身的二进制最高位为1,所以再异或 1B即 00001110 xor 00011011
得到 00010101 即:15
3)乘03时:
x * 03,结果为 (x * 02) xor(这里xor指异或) x,即,先乘02再异或本身,计算方法和上面一样。
举例:03 * 30
03 * 30=(30 * 02)xor 30=60 xor 30=50
其中列混合操作:列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵
矩阵乘法如图所示:
5.轮密钥加
步骤还是第一步那个步骤,把上一步产生的4x4矩阵与4x4密钥相加,但是这里要加的密钥不再是那个原始密钥了,新的密钥要通过那个原始密钥计算产生,下面讲一下计算方法。
5.1密钥拓展算法
密钥拓展原理图如下:
密钥扩展过程说明:
1) 将种子密钥按图(a)的格式排列,其中k0、k1、……、k15依次表示种子密钥的一个字节;排列后用4个32比特的字表示,分别记为w[0]、w[1]、w[2]、w[3];
2) 按照如下方式,依次求解w[j],其中j是整数并且属于[4,43];
3) 若j%4=0,则w[j]=w[j-4]⊕g(w[j-1]),否则w[j]=w[j-4]⊕w[j-1];
函数g的流程说明:
a) 将w循环左移8比特;
b) 分别对每个字节做S盒置换;
c) 与32比特的常量(RC[j/4],0,0,0)进行异或,RC是一个一维数组,其值如下。(RC的值只需要有10个,而此处用了11个,实际上RC[0]在运算中没有用到,增加RC[0]是为了便于程序中用数组表示。由于j的最小取值是4,j/4的最小取值则是1,因此不会产生错误。)
RC = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36}
5.2 密钥扩展核心规则
1)核心公式:
一般情况:W[i] = W[i-4] ⊕ W[i-1]
当i%4=0时:W[i] = W[i-4] ⊕ T(W[i-1])
T()函数:RotWord → SubWord → ⊕ Rcon
2)关键操作定义
操作 | 说明 |
---|---|
RotWord | 循环左移1字节([a,b,c,d] → [b,c,d,a]) |
SubWord | 对每个字节查S盒(与加密流程的S盒相同) |
Rcon | 轮常数,按Rcon[j] = (0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36) |
3)详细步骤示例
示例主密钥**:2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c
(分组为4个字:W0=2b7e1516, W1=28aed2a6, W2=abf71588, W3=09cf4f3c)
步骤1:生成前4个字(直接复制主密钥)
W0 = 2b7e1516
W1 = 28aed2a6
W2 = abf71588
W3 = 09cf4f3c
步骤2:计算W4(i=4,满足i%4=0)
RotWord(W3):09cf4f3c → cf4f3c09
SubWord:
cf → 8a,4f → 84,3c → eb,09 → 01 → 8a84eb01
异或Rcon[1]:8a84eb01 ⊕ 01000000 = 8b84eb01
计算W4:W0 ⊕ 结果
2b7e1516 ⊕ 8b84eb01 = a0fae17`
结果:W4 = a0fae17
步骤3:计算W5~W7(i=5,6,7,普通情况)
W5 = W1 ⊕ W4 = 28aed2a6 ⊕ a0fae17 = 88542cb1
W6 = W2 ⊕ W5 = abf71588 ⊕ 88542cb1 = 23a33939
W7 = W3 ⊕ W6 = 09cf4f3c ⊕ 23a33939 = 2a6c7605
步骤4:计算W8(i=8,满足i%4=0)
RotWord(W7):2a6c7605 → 6c76052a
SubWord:
6c → 50, 76 → 6c, 05 → 6b, 2a → c5 → 506c6bc5
异或Rcon[2]:506c6bc5 ⊕ 02000000 = 526c6bc5
计算W8:W4 ⊕ 结果
a0fae17 ⊕ 526c6bc5 = f296384d`
结果:W8 = f296384d
6.最终轮
将以上步骤重复10轮,就可以得到密文,但是需要注意最后一轮不需要列混淆,最后得到16字节密文。
最后贴出我生成加密代码
加密代码如下(示例):
// AES-128 S盒和逆S盒
static const uint8_t sbox[256] = {
0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16};
static const uint8_t inv_sbox[256] = {
0x52,0x09,0x6A,0xD5,0x30,0x36,0xA5,0x38,0xBF,0x40,0xA3,0x9E,0x81,0xF3,0xD7,0xFB,
0x7C,0xE3,0x39,0x82,0x9B,0x2F,0xFF,0x87,0x34,0x8E,0x43,0x44,0xC4,0xDE,0xE9,0xCB,
0x54,0x7B,0x94,0x32,0xA6,0xC2,0x23,0x3D,0xEE,0x4C,0x95,0x0B,0x42,0xFA,0xC3,0x4E,
0x08,0x2E,0xA1,0x66,0x28,0xD9,0x24,0xB2,0x76,0x5B,0xA2,0x49,0x6D,0x8B,0xD1,0x25,
0x72,0xF8,0xF6,0x64,0x86,0x68,0x98,0x16,0xD4,0xA4,0x5C,0xCC,0x5D,0x65,0xB6,0x92,
0x6C,0x70,0x48,0x50,0xFD,0xED,0xB9,0xDA,0x5E,0x15,0x46,0x57,0xA7,0x8D,0x9D,0x84,
0x90,0xD8,0xAB,0x00,0x8C,0xBC,0xD3,0x0A,0xF7,0xE4,0x58,0x05,0xB8,0xB3,0x45,0x06,
0xD0,0x2C,0x1E,0x8F,0xCA,0x3F,0x0F,0x02,0xC1,0xAF,0xBD,0x03,0x01,0x13,0x8A,0x6B,
0x3A,0x91,0x11,0x41,0x4F,0x67,0xDC,0xEA,0x97,0xF2,0xCF,0xCE,0xF0,0xB4,0xE6,0x73,
0x96,0xAC,0x74,0x22,0xE7,0xAD,0x35,0x85,0xE2,0xF9,0x37,0xE8,0x1C,0x75,0xDF,0x6E,
0x47,0xF1,0x1A,0x71,0x1D,0x29,0xC5,0x89,0x6F,0xB7,0x62,0x0E,0xAA,0x18,0xBE,0x1B,
0xFC,0x56,0x3E,0x4B,0xC6,0xD2,0x79,0x20,0x9A,0xDB,0xC0,0xFE,0x78,0xCD,0x5A,0xF4,
0x1F,0xDD,0xA8,0x33,0x88,0x07,0xC7,0x31,0xB1,0x12,0x10,0x59,0x27,0x80,0xEC,0x5F,
0x60,0x51,0x7F,0xA9,0x19,0xB5,0x4A,0x0D,0x2D,0xE5,0x7A,0x9F,0x93,0xC9,0x9C,0xEF,
0xA0,0xE0,0x3B,0x4D,0xAE,0x2A,0xF5,0xB0,0xC8,0xEB,0xBB,0x3C,0x83,0x53,0x99,0x61,
0x17,0x2B,0x04,0x7E,0xBA,0x77,0xD6,0x26,0xE1,0x69,0x14,0x63,0x55,0x21,0x0C,0x7D};
// 轮常数
static const uint8_t Rcon[11] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
// 密钥扩展
void KeyExpansion(const uint8_t* key, uint8_t* roundKey) {
uint8_t temp[4];
// 前16字节直接复制密钥
for (int i = 0; i < 16; ++i)
roundKey[i] = key[i];
// 生成后续轮密钥
for (int i = 4; i < 44; ++i) {
temp[0] = roundKey[(i-1)*4 + 0];
temp[1] = roundKey[(i-1)*4 + 1];
temp[2] = roundKey[(i-1)*4 + 2];
temp[3] = roundKey[(i-1)*4 + 3];
if (i % 4 == 0) {
// RotWord
uint8_t tmp = temp[0];
temp[0] = temp[1];
temp[1] = temp[2];
temp[2] = temp[3];
temp[3] = tmp;
// SubWord
temp[0] = sbox[temp[0]];
temp[1] = sbox[temp[1]];
temp[2] = sbox[temp[2]];
temp[3] = sbox[temp[3]];
// Rcon
temp[0] ^= Rcon[i/4];
}
roundKey[i*4 + 0] = roundKey[(i-4)*4 + 0] ^ temp[0];
roundKey[i*4 + 1] = roundKey[(i-4)*4 + 1] ^ temp[1];
roundKey[i*4 + 2] = roundKey[(i-4)*4 + 2] ^ temp[2];
roundKey[i*4 + 3] = roundKey[(i-4)*4 + 3] ^ temp[3];
}
}
// 轮密钥加
void AddRoundKey(uint8_t* state, const uint8_t* roundKey, int round) {
for (int i = 0; i < 16; ++i)
state[i] ^= roundKey[round*16 + i];
}
// 字节替代
void SubBytes(uint8_t* state) {
for (int i = 0; i < 16; ++i)
state[i] = sbox[state[i]];
}
// 行移位
void ShiftRows(uint8_t* state) {
uint8_t temp[16];
// 第0行不移位
temp[0] = state[0];
temp[4] = state[4];
temp[8] = state[8];
temp[12] = state[12];
// 第1行左移1字节
temp[1] = state[5];
temp[5] = state[9];
temp[9] = state[13];
temp[13] = state[1];
// 第2行左移2字节
temp[2] = state[10];
temp[6] = state[14];
temp[10] = state[2];
temp[14] = state[6];
// 第3行左移3字节
temp[3] = state[15];
temp[7] = state[3];
temp[11] = state[7];
temp[15] = state[11];
memcpy(state, temp, 16);
}
// 有限域乘法
uint8_t gf_mult(uint8_t a, uint8_t b) {
uint8_t p = 0;
for (int i = 0; i < 8; ++i) {
if (b & 1) p ^= a;
a = (a << 1) ^ (a & 0x80 ? 0x1b : 0);
b >>= 1;
}
return p;
}
// 列混淆
void MixColumns(uint8_t* state) {
uint8_t tmp[16];
for (int i = 0; i < 4; ++i) {
tmp[i*4+0] = gf_mult(0x02, state[i*4+0]) ^ gf_mult(0x03, state[i*4+1]) ^ state[i*4+2] ^ state[i*4+3];
tmp[i*4+1] = state[i*4+0] ^ gf_mult(0x02, state[i*4+1]) ^ gf_mult(0x03, state[i*4+2]) ^ state[i*4+3];
tmp[i*4+2] = state[i*4+0] ^ state[i*4+1] ^ gf_mult(0x02, state[i*4+2]) ^ gf_mult(0x03, state[i*4+3]);
tmp[i*4+3] = gf_mult(0x03, state[i*4+0]) ^ state[i*4+1] ^ state[i*4+2] ^ gf_mult(0x02, state[i*4+3]);
}
memcpy(state, tmp, 16);
}
// AES加密函数
void AES_Encrypt(uint8_t* state, const uint8_t* roundKey) {
AddRoundKey(state, roundKey, 0);
for (int round = 1; round < 10; ++round) {
SubBytes(state);
ShiftRows(state);
MixColumns(state);
AddRoundKey(state, roundKey, round);
}
// 最终轮
SubBytes(state);
ShiftRows(state);
AddRoundKey(state, roundKey, 10);
}
// 主函数
int main() {
char choice;
do {
uint8_t plaintext[16]; // 16字节明文
uint8_t key[16]; // 16字节密钥
uint8_t roundKey[176]; // 11轮密钥
// 输入明文(自动清空旧数据)
printf("\n请输入16字节明文(32位十六进制,如00112233445566778899AABBCCDDEEFF):\n");
for (int i = 0; i < 16; ++i) {
unsigned int temp;
scanf("%2x", &temp);
plaintext[i] = (uint8_t)temp;
}
// 输入密钥
printf("请输入16字节密钥(32位十六进制,如000102030405060708090A0B0C0D0E0F):\n");
for (int i = 0; i < 16; ++i) {
unsigned int temp;
scanf("%2x", &temp);
key[i] = (uint8_t)temp;
}
// 清空输入缓冲区残留字符
while (getchar() != '\n');
// 密钥扩展
KeyExpansion(key, roundKey);
// 加密
uint8_t ciphertext[16];
memcpy(ciphertext, plaintext, 16);
AES_Encrypt(ciphertext, roundKey);
// 输出结果
printf("\n加密结果:");
for (int i = 0; i < 16; ++i)
printf("%02X", ciphertext[i]);
printf("\n");
// 询问是否继续
printf("\n继续加密吗?(Y/N): ");
scanf("%c", &choice);
while (getchar() != '\n'); // 清空换行符
} while (toupper(choice) == 'Y'); // 输入Y/y继续
return 0;
}
总结
以上是我学习AES加密算法的心得,感觉比较难以理解的就是列混淆和用原始密钥生成扩展密钥的方法比较难以理解,我也是查询了好多博主的简介,总结出自己的一套学习教程,希望对大家有所帮助。至于解密,网上也有教程,逆反就行了,只需要熟悉其中步骤。本文也借用大量网络讲解与图片,加上一些个人觉得难以理解的地方进行详细描述了。如有错误,欢迎指正。