在我的上一篇文章AES基础知识和计算过程中,大概介绍了AES(Rijndael)加密的整个过程。那么在这一篇文章中,就来看一下AES在代码中是如何实现的,也有助于我们理解其中的一些细节。

  • 本篇文章所用的AES代码来源于Szymon Stefanek的开源C++代码

文章目录

  • 1 代码解析
  • 1.1 AES类
  • 1.2 初始化函数
  • 1.2.1 init:初始化变量
  • 1.2.2 keySched:轮密钥计算
  • 1.3 blockEncrypt:加密多个块
  • 1.3.1 encrypt:加密一个块
  • 1.4 blockEncrypt:解密多个块
  • 1.4.1 解密一个块
  • 2 总结


1 代码解析

1.1 AES类

首先来看一下AES的数据结构,看一下定义了什么变量和函数:

#define _MAX_ROUNDS      14
#define MAX_IV_SIZE      16

class Rijndael
{	
public:
	enum Direction { Encrypt , Decrypt };
	enum Mode { ECB , CBC , CFB1 };
	enum KeyLength { Key16Bytes , Key24Bytes , Key32Bytes };

	Rijndael();
	~Rijndael();
protected:
	enum State { Valid , Invalid };
	State     m_state;
	Mode      m_mode;
	Direction m_direction;
	uint8_t     m_initVector[MAX_IV_SIZE];
	uint32_t    m_uRounds;
	uint8_t     m_expandedKey[_MAX_ROUNDS+1][4][4];
public:
	int init(Mode mode,Direction dir,const uint8_t *key,KeyLength keyLen,uint8_t * initVector = 0);
	int blockEncrypt(const uint8_t *input, int inputLen, uint8_t *outBuffer);
	int padEncrypt(const uint8_t *input, int inputOctets, uint8_t *outBuffer);
	int blockDecrypt(const uint8_t *input, int inputLen, uint8_t *outBuffer);
	int padDecrypt(const uint8_t *input, int inputOctets, uint8_t *outBuffer);
protected:
	void keySched(uint8_t key[_MAX_KEY_COLUMNS][4]);
	void keyEncToDec();
	void encrypt(const uint8_t a[16], uint8_t b[16]);
	void decrypt(const uint8_t a[16], uint8_t b[16]);
};

接下来我们就一个个分析上面的函数,由于原始代码考虑到了不同的AES位数,为了代码的精简,这里假设下面代码使用的都是AES-128密钥。

1.2 初始化函数

1.2.1 init:初始化变量

首先来看一下初始化函数:

/* KeyLength这里默认为Key16Bytes,即AES128 */
int Rijndael::init(Mode mode,Direction dir,const uint8_t * key,KeyLength keyLen,uint8_t * initVector)
{
	/* 设置模式 */
	m_mode = mode;
	/* 方向:加密或解密 */
	m_direction = dir;
	/* 初始化向量 */
	for(int i = 0;i < 16;i++)	m_initVector[i] = initVector[i];
	/* AES-128加密轮数为10轮 */
	m_uRounds = 10;
	/* 将用户指定的AES128密钥填入一个8*4的数组中,实际上填充0~3行的0~3列 */
	uint8_t keyMatrix[8][4];
	for(uint32_t i = 0;i < 16;i++)	keyMatrix[i >> 2][i & 3] = key[i];
	/* 生成轮密钥 */
	keySched(keyMatrix);
	
	/* 如果方向位解密,则还需要调用keyEncToDec */
	if(m_direction == Decrypt)keyEncToDec();
	/* 初始化完成,设置状态为valid */
	m_state = Valid;
	return RIJNDAEL_SUCCESS;
}

1、mode
上面代码中首先初始化了AES的工作模式,AES主要有三种工作模式:
(1)CBC(密码分组链接模式)
在CBC模式下,明文被分割成固定长度(如128位)的数据块。每个数据块在加密之前都要与前一个加密后的数据块进行异或运算,然后再进行加密。这个异或运算的结果会影响后续数据块的加密,因此,每个数据块的加密都依赖于前一个数据块的加密结果。由于每个数据块的加密都依赖于前一块,因此解密时必须按顺序解密每个数据块,并使用前一个解密后的数据块进行异或运算。CBC模式可以提供较好的数据保密性和随机性,但是对于并行加密解密来说不是很友好。
(2)ECB(电子密码本模式)
在ECB模式下,明文被分割成固定长度的数据块,并且每个数据块都独立加密。也就是说,每个数据块都使用相同的加密算法和密钥进行独立加密,没有任何依赖关系。这样的独立加密会带来一个问题,即如果明文中有重复的数据块,那么它们加密后的结果也会是重复的,这可能会有安全漏洞。ECB模式便于并行加密解密,但是不适合加密连续的数据。
(3)CFB1(加密反馈模式1)
在CFB1模式下,明文会被分成连续的一位一位的数据,并使用前一个加密后的数据位作为密钥来加密当前的明文位。这样的加密方式在每一位数据加密时都会引入依赖关系,因此每一位数据的加密都会受到前面的数据的影响。CFB1模式可以提供保密性和数据一致性,但是会引入一定的延迟。

  • 本篇文章主要以CBC模式为例进行代码分析,这也是最常用的一种模式

2、initVector
从前面类的定义来看,m_initVector是一个128位的数组,该数组可以由用户自己定义。后续用到了再看一下具体是用来干什么的。

3、uKeyLenInBytes和m_uRounds
即AES密钥的长度,有128位、192位和256位,分别对应的加密轮数为10轮、12轮和14轮。

4、keyMatrix和keySched()
keyMatrix为初始密钥,而keySched函数则是通过初始密钥生成轮密钥。

1.2.2 keySched:轮密钥计算

现在来看一下刚刚的生成轮密钥的函数keySched的实现:

static uint32_t rcon[30]=
{ 
	0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
	0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
	0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc,
	0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4,
	0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91
};

static uint8_t S[256]=
{
	 99, 124, 119, 123, 242, 107, 111, 197,  48,   1, 103,  43, 254, 215, 171, 118, 
	202, 130, 201, 125, 250,  89,  71, 240, 173, 212, 162, 175, 156, 164, 114, 192, 
	183, 253, 147,  38,  54,  63, 247, 204,  52, 165, 229, 241, 113, 216,  49,  21, 
	  4, 199,  35, 195,  24, 150,   5, 154,   7,  18, 128, 226, 235,  39, 178, 117, 
	  9, 131,  44,  26,  27, 110,  90, 160,  82,  59, 214, 179,  41, 227,  47, 132, 
	 83, 209,   0, 237,  32, 252, 177,  91, 106, 203, 190,  57,  74,  76,  88, 207, 
	208, 239, 170, 251,  67,  77,  51, 133,  69, 249,   2, 127,  80,  60, 159, 168, 
	 81, 163,  64, 143, 146, 157,  56, 245, 188, 182, 218,  33,  16, 255, 243, 210, 
	205,  12,  19, 236,  95, 151,  68,  23, 196, 167, 126,  61, 100,  93,  25, 115, 
	 96, 129,  79, 220,  34,  42, 144, 136,  70, 238, 184,  20, 222,  94,  11, 219, 
	224,  50,  58,  10,  73,   6,  36,  92, 194, 211, 172,  98, 145, 149, 228, 121, 
	231, 200,  55, 109, 141, 213,  78, 169, 108,  86, 244, 234, 101, 122, 174,   8, 
	186, 120,  37,  46,  28, 166, 180, 198, 232, 221, 116,  31,  75, 189, 139, 138, 
	112,  62, 181, 102,  72,   3, 246,  14,  97,  53,  87, 185, 134, 193,  29, 158, 
	225, 248, 152,  17, 105, 217, 142, 148, 155,  30, 135, 233, 206,  85,  40, 223, 
	140, 161, 137,  13, 191, 230,  66, 104,  65, 153,  45,  15, 176,  84, 187,  22
};

void Rijndael::keySched(uint8_t key[8][4])
{
	unsigned j,rconpointer = 0;
	/* 对于AES128来说,密钥有4列 */
	unsigned uKeyColumns = m_uRounds - 6;  //4
	/* 拷贝用户提供的密钥到一个临时密钥数组中 */
	uint8_t tempKey[8][4];
	for(j = 0;j < uKeyColumns;j++)
	{
		*((uint32_t*)(tempKey[j])) = *((uint32_t*)(key[j]));
	}
	unsigned r = 0, t = 0;
	/* 拷贝用户提供的密钥到AES类中的轮密钥数组m_expandedKey中 */
	for(j = 0;(j < uKeyColumns) && (r <= m_uRounds); )  //实际该层循环运行了一次
	{
		for(;(j < uKeyColumns) && (t < 4); j++, t++)  //循环4次,赋值给m_expandedKey[0]中的4x4矩阵
		{
			*((uint32_t*)m_expandedKey[r][t]) = *((uint32_t*)tempKey[j]);
		}
		if(t == 4)
		{
			r++;
			t = 0;
		}
	}
	/* 此时m_expandedKey=tempKey=key的内容 */
	/* 计算接下来10轮的轮密钥:此时r=1,即第一轮密钥是用户提供的,后面的由代码进行扩充 */
	while(r <= m_uRounds)
	{
		/* 计算4的倍数那一列,即第0列和第3列T函数结果进行异或 */
		/* [0,1,2,3]对应[1,2,3,0]为字循环,然后在s盒进行字节代换 */
		tempKey[0][0] ^= S[tempKey[uKeyColumns-1][1]];
		tempKey[0][1] ^= S[tempKey[uKeyColumns-1][2]];
		tempKey[0][2] ^= S[tempKey[uKeyColumns-1][3]];
		tempKey[0][3] ^= S[tempKey[uKeyColumns-1][0]];
		/* 仅有4的倍数那一列的第一个元素进行轮常量异或 */
		tempKey[0][0] ^= rcon[rconpointer++];
		/* 计算其它3列:直接强制转化为uint32_t进行异或,一次计算一列 */
		for(j = 1; j < uKeyColumns; j++)
		{
			*((uint32_t*)tempKey[j]) ^= *((uint32_t*)tempKey[j-1]);
		}
		/* 将结果保存在m_expandedKey数组中,其中r为轮数 */
		for(j = 0; (j < uKeyColumns) && (r <= m_uRounds); )
		{
			for(; (j < uKeyColumns) && (t < 4); j++, t++)
			{
				*((uint32_t*)m_expandedKey[r][t]) = *((uint32_t*)tempKey[j]);
			}
			if(t == 4)
			{
				r++;
				t = 0;
			}
		}
	}		
}

这里主要分析一下最后计算轮密钥的while循环,每次循环计算一个4x4的轮密钥。一个轮密钥中的四列,只有第一列是4的倍数,另外三列直接通过异或操作就能得到。先来回忆一下上一篇文章轮密钥的计算方法:


如果这个索引不是4的倍数,则aes加密随机key aes加密代码_数据块
如果索引是4的倍数,则aes加密随机key aes加密代码_初始化_02。其中T函数包括:
①字循环:假设aes加密随机key aes加密代码_数据块_03从上到下为aes加密随机key aes加密代码_安全_04,则字循环后为aes加密随机key aes加密代码_算法_05
②字节代换:将字循环的结果使用S盒进行字节代换
③轮常量异或:将字节代换的结果和轮常量进行异或得到最终的aes加密随机key aes加密代码_安全_06


这里简单分析一下计算索引为4的倍数的这一行:tempKey[0][0] ^= S[tempKey[uKeyColumns-1][1]],其中tempKey[uKeyColumns-1][1]aes加密随机key aes加密代码_数据块_03在字循环后的第一个元素。S[tempKey[uKeyColumns-1][1]]则是进行了S盒字节代换,S盒实际上是一个uint8_t S[256]的数组。tempKey[0][0]就是aes加密随机key aes加密代码_数据块_08,异或的结果保存在tempKey[0][0]中。

从代码中来看,只有第一个元素,即tempKey[0][0],需要异或轮常量rcon(round constant)。所以理论和实际实现有一定的差异,当然你也可以全部都异或,不同人写的代码在它的实现细节可能会有一些不同。和CRC的计算一样,你还可以有一些CRC反转的操作。对于AES来说,我们只需要保证加密和解密遵循同一个规则就行了。

1.3 blockEncrypt:加密多个块

在前面定义的AES类中的加解密函数有blockEncryptpadEncryptblockDecryptpadDecrypt。对于block的函数来说,输入的长度必须为16字节的倍数,最后多出的部分会被丢弃;而pad函数会在最后不足16字节的数据后面补齐16字节。本篇文章就以block函数进行分析,先来看看块加密函数blockEncrypt

int Rijndael::blockEncrypt(const uint8_t *input,int inputLen,uint8_t *outBuffer)
{
	int i, k, numBlocks;
	uint8_t block[16], iv[4][4];
	/* block的数量 */
	numBlocks = inputLen/128;
	/* 计算第一个block */
	((uint32_t*)block)[0] = ((uint32_t*)m_initVector)[0] ^ ((uint32_t*)input)[0];
	((uint32_t*)block)[1] = ((uint32_t*)m_initVector)[1] ^ ((uint32_t*)input)[1];
	((uint32_t*)block)[2] = ((uint32_t*)m_initVector)[2] ^ ((uint32_t*)input)[2];
	((uint32_t*)block)[3] = ((uint32_t*)m_initVector)[3] ^ ((uint32_t*)input)[3];
	encrypt(block,outBuffer);
	input += 16;
	/* 计算剩下的block */
	for(i = numBlocks - 1;i > 0;i--)
	{
		((uint32_t*)block)[0] = ((uint32_t*)outBuffer)[0] ^ ((uint32_t*)input)[0];
		((uint32_t*)block)[1] = ((uint32_t*)outBuffer)[1] ^ ((uint32_t*)input)[1];
		((uint32_t*)block)[2] = ((uint32_t*)outBuffer)[2] ^ ((uint32_t*)input)[2];
		((uint32_t*)block)[3] = ((uint32_t*)outBuffer)[3] ^ ((uint32_t*)input)[3];
		outBuffer += 16;
		encrypt(block,outBuffer);
		input += 16;
	}
	
	return 128 * numBlocks;
}

可以看出,每个block都要与上一轮encrypt加密的结果进行异或作为下一轮encrypt加密的输入,而第一个block就与用户在初始化时提供的m_initVector进行异或,而后面的block的m_initVector就是前一轮block加密的结果。最终的结果保存在outBuffer中,其中最关键的函数就是encrypt,下面来看一下这个函数:

1.3.1 encrypt:加密一个块

void Rijndael::encrypt(const uint8_t a[16], uint8_t b[16])
{
	unsigned r;
	uint8_t temp[4][4];
	/* 输入的128位异或第一轮用户提供的轮密钥Round 0 */
    *((uint32_t*)temp[0]) = *((uint32_t*)(a   )) ^ *((uint32_t*)m_expandedKey[0][0]);
    *((uint32_t*)temp[1]) = *((uint32_t*)(a+ 4)) ^ *((uint32_t*)m_expandedKey[0][1]);
    *((uint32_t*)temp[2]) = *((uint32_t*)(a+ 8)) ^ *((uint32_t*)m_expandedKey[0][2]);
    *((uint32_t*)temp[3]) = *((uint32_t*)(a+12)) ^ *((uint32_t*)m_expandedKey[0][3]);
    /* 每一列分别使用T1~T4进行行移位和字节替换,注意temp中的索引有行移位 */
    *((uint32_t*)(b    )) = *((uint32_t*)T1[temp[0][0]])
						^ *((uint32_t*)T2[temp[1][1]])
						^ *((uint32_t*)T3[temp[2][2]]) 
						^ *((uint32_t*)T4[temp[3][3]]);
    *((uint32_t*)(b + 4)) = *((uint32_t*)T1[temp[1][0]])
						^ *((uint32_t*)T2[temp[2][1]])
						^ *((uint32_t*)T3[temp[3][2]]) 
						^ *((uint32_t*)T4[temp[0][3]]);
    *((uint32_t*)(b + 8)) = *((uint32_t*)T1[temp[2][0]])
						^ *((uint32_t*)T2[temp[3][1]])
						^ *((uint32_t*)T3[temp[0][2]]) 
						^ *((uint32_t*)T4[temp[1][3]]);
    *((uint32_t*)(b +12)) = *((uint32_t*)T1[temp[3][0]])
						^ *((uint32_t*)T2[temp[0][1]])
						^ *((uint32_t*)T3[temp[1][2]]) 
						^ *((uint32_t*)T4[temp[2][3]]);
	/* 计算剩下的8轮:Round1到Round 8 */
	for(r = 1; r < m_uRounds-1; r++)
	{
		/* 上一轮的结果作为这一轮的输入,异或下一轮轮密钥 */
		*((uint32_t*)temp[0]) = *((uint32_t*)(b   )) ^ *((uint32_t*)m_expandedKey[r][0]);
		*((uint32_t*)temp[1]) = *((uint32_t*)(b+ 4)) ^ *((uint32_t*)m_expandedKey[r][1]);
		*((uint32_t*)temp[2]) = *((uint32_t*)(b+ 8)) ^ *((uint32_t*)m_expandedKey[r][2]);
		*((uint32_t*)temp[3]) = *((uint32_t*)(b+12)) ^ *((uint32_t*)m_expandedKey[r][3]);

		*((uint32_t*)(b    )) = *((uint32_t*)T1[temp[0][0]])
							^ *((uint32_t*)T2[temp[1][1]])
							^ *((uint32_t*)T3[temp[2][2]]) 
							^ *((uint32_t*)T4[temp[3][3]]);
		*((uint32_t*)(b + 4)) = *((uint32_t*)T1[temp[1][0]])
							^ *((uint32_t*)T2[temp[2][1]])
							^ *((uint32_t*)T3[temp[3][2]]) 
							^ *((uint32_t*)T4[temp[0][3]]);
		*((uint32_t*)(b + 8)) = *((uint32_t*)T1[temp[2][0]])
							^ *((uint32_t*)T2[temp[3][1]])
							^ *((uint32_t*)T3[temp[0][2]]) 
							^ *((uint32_t*)T4[temp[1][3]]);
		*((uint32_t*)(b +12)) = *((uint32_t*)T1[temp[3][0]])
							^ *((uint32_t*)T2[temp[0][1]])
							^ *((uint32_t*)T3[temp[1][2]]) 
							^ *((uint32_t*)T4[temp[2][3]]);
	}
	/* 最后一轮的处理 */
	/* 将结果异或Round 9的密钥 */
	*((uint32_t*)temp[0]) = *((uint32_t*)(b   )) ^ *((uint32_t*)m_expandedKey[m_uRounds-1][0]);
	*((uint32_t*)temp[1]) = *((uint32_t*)(b+ 4)) ^ *((uint32_t*)m_expandedKey[m_uRounds-1][1]);
	*((uint32_t*)temp[2]) = *((uint32_t*)(b+ 8)) ^ *((uint32_t*)m_expandedKey[m_uRounds-1][2]);
	*((uint32_t*)temp[3]) = *((uint32_t*)(b+12)) ^ *((uint32_t*)m_expandedKey[m_uRounds-1][3]);
	/* 前面得到的4x4结果作为索引,行移位后在T1盒中进行代换,前面1-8轮的不同是,它直接取第1列的数据 */
	b[ 0] = T1[temp[0][0]][1];
	b[ 1] = T1[temp[1][1]][1];
	b[ 2] = T1[temp[2][2]][1];
	b[ 3] = T1[temp[3][3]][1];
	b[ 4] = T1[temp[1][0]][1];
	b[ 5] = T1[temp[2][1]][1];
	b[ 6] = T1[temp[3][2]][1];
	b[ 7] = T1[temp[0][3]][1];
	b[ 8] = T1[temp[2][0]][1];
	b[ 9] = T1[temp[3][1]][1];
	b[10] = T1[temp[0][2]][1];
	b[11] = T1[temp[1][3]][1];
	b[12] = T1[temp[3][0]][1];
	b[13] = T1[temp[0][1]][1];
	b[14] = T1[temp[1][2]][1];
	b[15] = T1[temp[2][3]][1];
	/* 最后将结果异或上最后一轮密钥Round 10得到此次加密的结果 */
	*((uint32_t*)(b   )) ^= *((uint32_t*)m_expandedKey[m_uRounds][0]);
	*((uint32_t*)(b+ 4)) ^= *((uint32_t*)m_expandedKey[m_uRounds][1]);
	*((uint32_t*)(b+ 8)) ^= *((uint32_t*)m_expandedKey[m_uRounds][2]);
	*((uint32_t*)(b+12)) ^= *((uint32_t*)m_expandedKey[m_uRounds][3]);
}

上面函数的实现过程与我们上一节标准AES有一些不同:
(1)文中的T1T2T3T4都是static uint8_t Tn[256][4]的数组,它们作为加密过程中每一列的S盒,而没有使用前面生成轮密钥的S盒,这样是为了增加加密的复杂性。
(2)在上一节文章中有介绍,添加轮密钥后,进行字节替换,然后才是行移位。但在上面的代码中,先进行了行移位,再使用T1~T4四个S盒进行了字节替换。
(3)上面的代码中没有列混合变换,这是因为矩阵的乘法很耗时间,为了追求速度,这里就省略了。
(4)上面代码中的第九轮与前面的几轮又有些许的不同,之前都是直接在T1T4的表中替换,将每4个uint8_t的元素分别加入T盒进行替换得到一个uint32_t的结果后进行异或得到结果。而第9轮则是直接使用T1查表得到的第1列元素,作为第九轮的4x4结果。
(5)AES有最标准的理论,但实际实现时会因为实际情况而有一些差异。就像之前我写的MD5代码实现详解中,我找到的代码中的非线性操作也和标准的md5要求的非线性操作不同。这里不对这些差异进行深究,我们只要保证解密过程完全与加密过程对称就行了。

1.4 blockEncrypt:解密多个块

实际上解密就是加密的逆过程,之前的运算之所以主要是异或运算,是因为aes加密随机key aes加密代码_数据块_09

int Rijndael::blockDecrypt(const uint8_t *input, int inputLen, uint8_t *outBuffer)
{
	int i, k, numBlocks;
	uint8_t block[16], iv[4][4];
	/* 若长度不是128的倍数,多余的将被忽略 */
	numBlocks = inputLen/128;
	/* iv赋值为用户提供的初始化向量m_initVector  */
	*((uint32_t*)iv[0]) = *((uint32_t*)(m_initVector  ));
	*((uint32_t*)iv[1]) = *((uint32_t*)(m_initVector+ 4));
	*((uint32_t*)iv[2]) = *((uint32_t*)(m_initVector+ 8));
	*((uint32_t*)iv[3]) = *((uint32_t*)(m_initVector+12));
	/* 解密并将结果保存到outBuffer */
	for (i = numBlocks; i > 0; i--)
	{
		decrypt(input, block);
		/* 第一个block用的是m_initVector 进行异或的 */
		((uint32_t*)block)[0] ^= *((uint32_t*)iv[0]);
		((uint32_t*)block)[1] ^= *((uint32_t*)iv[1]);
		((uint32_t*)block)[2] ^= *((uint32_t*)iv[2]);
		((uint32_t*)block)[3] ^= *((uint32_t*)iv[3]);
		/* 后面的block用的是前一轮block加密后的结果(这里的input)进行异或的 */
		*((uint32_t*)iv[0]) = ((uint32_t*)input)[0]; 
		((uint32_t*)outBuffer)[0] = ((uint32_t*)block)[0];
		*((uint32_t*)iv[1]) = ((uint32_t*)input)[1];
		 ((uint32_t*)outBuffer)[1] = ((uint32_t*)block)[1];
		*((uint32_t*)iv[2]) = ((uint32_t*)input)[2];
		((uint32_t*)outBuffer)[2] = ((uint32_t*)block)[2];
		*((uint32_t*)iv[3]) = ((uint32_t*)input)[3];
		((uint32_t*)outBuffer)[3] = ((uint32_t*)block)[3];

		input += 16;
		outBuffer += 16;
	}
	
	return 128*numBlocks;
}

解密的核心在decrypt函数中,下面来分析一下这个函数。

1.4.1 解密一个块

现在来分析一下解密一个块的函数:

void Rijndael::decrypt(const uint8_t a[16], uint8_t b[16])
{
	int r;
	uint8_t temp[4][4];
	/* 加密的反过程:异或上round 10轮密钥得到round9的结果 */
    *((uint32_t*)temp[0]) = *((uint32_t*)(a   )) ^ *((uint32_t*)m_expandedKey[m_uRounds][0]);
    *((uint32_t*)temp[1]) = *((uint32_t*)(a+ 4)) ^ *((uint32_t*)m_expandedKey[m_uRounds][1]);
    *((uint32_t*)temp[2]) = *((uint32_t*)(a+ 8)) ^ *((uint32_t*)m_expandedKey[m_uRounds][2]);
    *((uint32_t*)temp[3]) = *((uint32_t*)(a+12)) ^ *((uint32_t*)m_expandedKey[m_uRounds][3]);

    *((uint32_t*)(b   )) = *((uint32_t*)T5[temp[0][0]])
           ^ *((uint32_t*)T6[temp[3][1]])
           ^ *((uint32_t*)T7[temp[2][2]]) 
           ^ *((uint32_t*)T8[temp[1][3]]);
	*((uint32_t*)(b+ 4)) = *((uint32_t*)T5[temp[1][0]])
           ^ *((uint32_t*)T6[temp[0][1]])
           ^ *((uint32_t*)T7[temp[3][2]]) 
           ^ *((uint32_t*)T8[temp[2][3]]);
	*((uint32_t*)(b+ 8)) = *((uint32_t*)T5[temp[2][0]])
           ^ *((uint32_t*)T6[temp[1][1]])
           ^ *((uint32_t*)T7[temp[0][2]]) 
           ^ *((uint32_t*)T8[temp[3][3]]);
	*((uint32_t*)(b+12)) = *((uint32_t*)T5[temp[3][0]])
           ^ *((uint32_t*)T6[temp[2][1]])
           ^ *((uint32_t*)T7[temp[1][2]]) 
           ^ *((uint32_t*)T8[temp[0][3]]);
	for(r = m_uRounds-1; r > 1; r--)
	{
		*((uint32_t*)temp[0]) = *((uint32_t*)(b   )) ^ *((uint32_t*)m_expandedKey[r][0]);
		*((uint32_t*)temp[1]) = *((uint32_t*)(b+ 4)) ^ *((uint32_t*)m_expandedKey[r][1]);
		*((uint32_t*)temp[2]) = *((uint32_t*)(b+ 8)) ^ *((uint32_t*)m_expandedKey[r][2]);
		*((uint32_t*)temp[3]) = *((uint32_t*)(b+12)) ^ *((uint32_t*)m_expandedKey[r][3]);
		*((uint32_t*)(b   )) = *((uint32_t*)T5[temp[0][0]])
           ^ *((uint32_t*)T6[temp[3][1]])
           ^ *((uint32_t*)T7[temp[2][2]]) 
           ^ *((uint32_t*)T8[temp[1][3]]);
		*((uint32_t*)(b+ 4)) = *((uint32_t*)T5[temp[1][0]])
           ^ *((uint32_t*)T6[temp[0][1]])
           ^ *((uint32_t*)T7[temp[3][2]]) 
           ^ *((uint32_t*)T8[temp[2][3]]);
		*((uint32_t*)(b+ 8)) = *((uint32_t*)T5[temp[2][0]])
           ^ *((uint32_t*)T6[temp[1][1]])
           ^ *((uint32_t*)T7[temp[0][2]]) 
           ^ *((uint32_t*)T8[temp[3][3]]);
		*((uint32_t*)(b+12)) = *((uint32_t*)T5[temp[3][0]])
           ^ *((uint32_t*)T6[temp[2][1]])
           ^ *((uint32_t*)T7[temp[1][2]]) 
           ^ *((uint32_t*)T8[temp[0][3]]);
	}
 
	*((uint32_t*)temp[0]) = *((uint32_t*)(b   )) ^ *((uint32_t*)m_expandedKey[1][0]);
	*((uint32_t*)temp[1]) = *((uint32_t*)(b+ 4)) ^ *((uint32_t*)m_expandedKey[1][1]);
	*((uint32_t*)temp[2]) = *((uint32_t*)(b+ 8)) ^ *((uint32_t*)m_expandedKey[1][2]);
	*((uint32_t*)temp[3]) = *((uint32_t*)(b+12)) ^ *((uint32_t*)m_expandedKey[1][3]);
	b[ 0] = S5[temp[0][0]];
	b[ 1] = S5[temp[3][1]];
	b[ 2] = S5[temp[2][2]];
	b[ 3] = S5[temp[1][3]];
	b[ 4] = S5[temp[1][0]];
	b[ 5] = S5[temp[0][1]];
	b[ 6] = S5[temp[3][2]];
	b[ 7] = S5[temp[2][3]];
	b[ 8] = S5[temp[2][0]];
	b[ 9] = S5[temp[1][1]];
	b[10] = S5[temp[0][2]];
	b[11] = S5[temp[3][3]];
	b[12] = S5[temp[3][0]];
	b[13] = S5[temp[2][1]];
	b[14] = S5[temp[1][2]];
	b[15] = S5[temp[0][3]];
	*((uint32_t*)(b   )) ^= *((uint32_t*)m_expandedKey[0][0]);
	*((uint32_t*)(b+ 4)) ^= *((uint32_t*)m_expandedKey[0][1]);
	*((uint32_t*)(b+ 8)) ^= *((uint32_t*)m_expandedKey[0][2]);
	*((uint32_t*)(b+12)) ^= *((uint32_t*)m_expandedKey[0][3]);
}

这里的代码又将整个解密的过程变得更复杂了一些。在前面加密的过程中,第9轮的结果是使用T1盒查表得到的第1列元素组成的,但这里的9~2轮的解密都是采用T5~T8盒进行代换。所以这里每一轮解密出来的输出与我们之前每一轮加密得到的输出并不一样。到最后采用uint8_t S5[256]盒进行查表得到round1的结果,最后再异或round0的轮密钥得到解密结果。

也就是说,这里T1~T8盒和S5(特别与T1有一定的关系)都是相互联系的,一个用来加密查表,一个用来解密查表,而加解密的表又不是直接可逆的,而是每一轮解密的结果又多了一层加密,直到解算到最后一轮,这一层加密可以采用S5盒来解开。

这里就没有找到这些T盒和S盒的生成原理,如果自己去逆向推导就比较耗时间,但我们主要是为了学习AES加解密的一个流程,所以本篇文章不深入研究这些替换数组的生成原理。

2 总结

AES是一种对称加密算法,被广泛应用于保护数据的机密性和安全性。在AES加解密的过程中,使用相同的密钥进行加密和解密操作,因此被归类为对称加密算法。本篇文章也通过代码的讲解,加深了我们对AES加解密的流程的理解。

  • 代码工程下载:AES加解密算法(Rijndael) C++代码