推一下大佬写的文章,本文中部分内容转载自此处:AES加密算法的详细介绍与实现

算法简介

高级加密标准(AES,Advanced Encryption Strandard)为最常见对称加密算法

加密流程如下:

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwMjE5MDgyOTA5Njg4.png

AES为分组密码,加密过程中将明文分成多个长度相同的组,每次加密一组数据,直到加密完整个明文。

在AES标准规范中,分组长度只能是128位,每个分组为16个字节(每个字节8位)。

密钥的长度可以使用128位、192位或256位。

密钥的长度不同,推荐的加密轮数也不同,如下表所示:

AES 密钥长度(32位比特字) 分组长度(32位比特字) 加密轮数
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14

以AES-128为例,128位的明文分组P和密钥K都被分成了16个字节,分别记为 P = P0 P1 …… P15 和 K = K0 K1 …… K15

一般地,明文用字节为单位的正方形矩阵描述,称为状态矩阵

在算法的每一轮中,状态矩阵的内容不断变化最后作为密文输出。

矩阵中字节的排列顺序为从上到下、从左至右依次排列:

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwMjE5MTMyNTQ4OTA2.png

AES的整体结构如下图所示,其中的W[0,3]是指W[0]、W[1]、W[2]和W[3]串联组成的128位密钥。

加密的第1轮到第9轮的轮函数一样,包括4个操作:字节代换、行位移、列混合和轮密钥加。最后一轮迭代不执行列混合。

另外,在第一轮迭代之前,先将明文和原始密钥进行一次异或加密操作。

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwMjE5MTYxMjAyNDg1.png

算法操作

字节代换

AES定义了一个S盒和一个逆S盒。

其中字节代换操作用到S盒

字节代换逆操作用到逆S盒

字节代换操作

状态矩阵中的元素按照以下方式映成为一个一个新的字节:将该字节的高四位作为行值,低4位作为列值,取出S盒或者逆S盒中对应的行的元素作为输出。

例如:加密时输出的字节S1为0x12,则查S盒的第0x01行盒第0x02列,得到值0xc9,然后替换S1原有的0x12为0xc9,如下图所示:

20181213112210707.png

字节代换代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/****根据索引从S盒中获得元素****/
static int getNumFromSBox(int index)
{
int row = getLeft4Bit(index);
int col = getRight4Bit(index);
return S[row][col];
}


/****字节代换****/

static void subBytes(int array[4][4])
{
for(int i = 0;i < 4;i++)
for(int j = 0;j < 4;j++)
array[i][j] = getNumFromSBox(aeeay[i][j]);
}

字节代换逆操作

与字节代换操作一致,只是由S盒变为了逆S盒

行移位

行移位操作

左循环移位操作。当密钥长度为128比特时,状态矩阵的第0行左移0字节,第1行左移1字节,第2行左移2字节,第三行左移3字节,如下图所示:

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwMjE5MTc0MDE1MTY3.png

行移位的实现
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
/****将数组中的元素左移step位****/

static void leftLoop4int(int array[4],int step)
{
int temp[4];
for(int i = 0;i < 4;i++)
temp[i] = array[i];

int index = step % 4 == 0 ? 0 : step % 4;
for(int i = 0;i < 4;i++)
{
array[i] = temp[index];
index ++;
index = index % 4;
}
}


/****行移位****/

static void shiftRows(int array[4][4])
{
int rowTwo[4],rowThree[4],rowFour[4];
//复制状态矩阵的第2,3,4行
for(int i = 0,i < 4;i++)
{
rowTwo[i] = array[1][i];
rowThree[i] = array[2][]i];
rowFour[i] = array[3][i];
}
//循环左移相应的位数
leftLoop4int(rowTwo ,1);
leftLoop4int(rowThree,2);
leftLoop4int(rowFour, 3);
//将左移后的行复制回状态矩阵中
for(int i = 0;i < 4;i++)
{
array[1][i] = rowTwo[i];
array[2][i] = rowThree[i];
array[3][i] = rowFour[i];
}
}

行移位的逆变换

将状态矩阵中的每一行执行相反的移位操作,即左移变为右移

列混合

列混合操作

经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵

如图:

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwMjE5MjAzMzQ2NDM2.png

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwMjE5MjAzNzQyNTE2.png

列混合的实现
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/****列混合要用到的矩阵****/

static const int colM[4][4] = { 2,3,1,1,
1,2,3,1,
1,1,2,3,
3,1,1,2 };


static int GFMul2(int s)
{
int result = s << 1;
int a7 = result $ 0x00000100;

if(a7 != 0)
{
result = result & 0x000000ff;
result = result ^ 0x1b;
}

return result;
}


static int GFMul3(int s)
{
return GFMul2(s) ^ s;
}

/****GF上的二元运算****/
static int GFMul(int n, int s)
{
int result;

if(n == 1)
result = s;
else if(n ==2)
result = GFMul2(s);
else if(n == 3)
result = GFMul3(s);
else if(n == 0x9)
result = GFMul9(s);
else if(n == 0xb)//11
result = GFMul11(s);
else if(n == 0xd)//13
result = GFMul13(s);
else if(n == 0xe)//14
result = GFMul14(s);

return result;
}

/****列混合****/
static void mixColumns(int array[4][4])
{
int tempArray[4][4];

for(int i = 0; i < 4; i++)
for(int j = 0; j < 4; j++)
tempArray[i][j] = array[i][j];

for(int i = 0; i < 4; i++)
for(int j = 0; j < 4; j++){
array[i][j] = GFMul(colM[i][0],tempArray[0][j]) ^ GFMul(colM[i][1],tempArray[1][j])
^ GFMul(colM[i][2],tempArray[2][j]) ^ GFMul(colM[i][3], tempArray[3][j]);
}
}

列混合逆运算

可由下图的矩阵乘法定义:

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwMjE5MjExMTM5NzUy.png

可以验证:逆变换矩阵同正变换矩阵的乘积恰好为单位矩阵

轮密钥加

将128为轮密钥Ki同状态矩阵中的数据进行逐位异或操作。

其中,密钥Ki中每个字W[4i],W[4i+1],W[4i+2],W[4i+3]为32位比特字,包含4个字节。

可以看成S0 S1 S2 S3 组成的32位字与W[4i]的异或运算。

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwMjIwMDgwNTEyMDg2.png

轮密钥加的逆运算同正向的轮密钥加运算完全一致,因为异或的逆操作是其自身。

轮密钥加的实现

1
2
3
4
5
6
7
8
9
10
11
12
/***轮密钥加****/
static void addRoundKey(int array[4][4], int round) {
int warray[4];
for(int i = 0; i < 4; i++) {

splitIntToArray(w[ round * 4 + i], warray);

for(int j = 0; j < 4; j++) {
array[j][i] = array[j][i] ^ warray[j];
}
}
}

密钥扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//密钥对应的扩展数组
static int w[44];

/**
* 扩展密钥,结果是把w[44]中的每个元素初始化
*/
static void extendKey(char *key) {
for(int i = 0; i < 4; i++)
w[i] = getWordFromStr(key + i * 4);

for(int i = 4, j = 0; i < 44; i++) {
if( i % 4 == 0) {
w[i] = w[i - 4] ^ T(w[i - 1], j);
j++;//下一轮
}else {
w[i] = w[i - 4] ^ w[i - 1];
}
}
}

T()函数的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 常量轮值表
*/
static const int Rcon[10] = { 0x01000000, 0x02000000,
0x04000000, 0x08000000,
0x10000000, 0x20000000,
0x40000000, 0x80000000,
0x1b000000, 0x36000000 };
/**
* 密钥扩展中的T函数
*/
static int T(int num, int round) {
int numArray[4];
splitIntToArray(num, numArray);
leftLoop4int(numArray, 1);//字循环

//字节代换
for(int i = 0; i < 4; i++)
numArray[i] = getNumFromSBox(numArray[i]);

int result = mergeArrayToInt(numArray);
return result ^ Rcon[round];
}

AES解密函数

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
49
/**
* 参数 c: 密文的字符串数组。
* 参数 clen: 密文的长度。
* 参数 key: 密钥的字符串数组。
*/
void deAes(char *c, int clen, char *key) {

int keylen = strlen(key);
if(clen == 0 || clen % 16 != 0) {
printf("密文字符长度必须为16的倍数!现在的长度为%d\n",clen);
exit(0);
}

if(!checkKeyLen(keylen)) {
printf("密钥字符长度错误!长度必须为16、24和32。当前长度为%d\n",keylen);
exit(0);
}

extendKey(key);//扩展密钥
int cArray[4][4];
for(int k = 0; k < clen; k += 16) {
convertToIntArray(c + k, cArray);


addRoundKey(cArray, 10);

int wArray[4][4];
for(int i = 9; i >= 1; i--) {
deSubBytes(cArray);

deShiftRows(cArray);

deMixColumns(cArray);
getArrayFrom4W(i, wArray);
deMixColumns(wArray);

addRoundTowArray(cArray, wArray);
}

deSubBytes(cArray);

deShiftRows(cArray);

addRoundKey(cArray, 0);

convertArrayToStr(cArray, c + k);

}
}