当前位置: 首页 > news >正文

AES加密(2):AES代码实现解析

在我的上一篇文章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      16class 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的倍数,则 W i = W i − 4 ⊕ W i − 1 W_i=W_{i-4} \oplus W_{i-1} Wi=Wi4Wi1
如果索引是4的倍数,则 W i = W i − 4 ⊕ T ( W i − 1 ) W_i=W_{i-4} \oplus T(W_{i-1}) Wi=Wi4T(Wi1)。其中T函数包括:
①字循环:假设 W i − 1 W_{i-1} Wi1从上到下为 [ a 0 , a 1 , a 2 , a 3 ] [a_0,a_1,a_2,a_3] [a0,a1,a2,a3],则字循环后为 [ a 1 , a 2 , a 3 , a 0 ] [a_1,a_2,a_3,a_0] [a1,a2,a3,a0]
②字节代换:将字循环的结果使用S盒进行字节代换
③轮常量异或:将字节代换的结果和轮常量进行异或得到最终的 T ( W i − 1 ) T(W_{i-1}) T(Wi1)


这里简单分析一下计算索引为4的倍数的这一行:tempKey[0][0] ^= S[tempKey[uKeyColumns-1][1]],其中tempKey[uKeyColumns-1][1] W i − 1 W_{i-1} Wi1在字循环后的第一个元素。S[tempKey[uKeyColumns-1][1]]则是进行了S盒字节代换,S盒实际上是一个uint8_t S[256]的数组。tempKey[0][0]就是 W i − 4 W_{i-4} Wi4,异或的结果保存在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:解密多个块

实际上解密就是加密的逆过程,之前的运算之所以主要是异或运算,是因为 a ⊕ b ⊕ a = b a\oplus b\oplus a = b aba=b

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++代码

相关文章:

AES加密(2):AES代码实现解析

在我的上一篇文章AES基础知识和计算过程中&#xff0c;大概介绍了AES(Rijndael)加密的整个过程。那么在这一篇文章中&#xff0c;就来看一下AES在代码中是如何实现的&#xff0c;也有助于我们理解其中的一些细节。 本篇文章所用的AES代码来源于Szymon Stefanek的开源C代码 文章…...

SpringBoot项目通过分词器生成词云

目录 前言一、词云是什么&#xff1f;二、使用步骤1.引入依赖2.application.yml3.Controller4.分词工具类4.词云生成工具类、支持输出文件和字节流 注意 前言 公司项目涉及到员工任务管理&#xff0c;需要从员工任务中获取任务信息生成个人词云图&#xff0c;可以把员工任务中…...

Nacos 配置管理及相关使用

文章目录 Nacos 配置管理一、统一配置管理1、在Nacos 中添加配置文件2、从微服务拉取配置3、配置实现步骤&#xff08;1&#xff09;引入 nacos-config 依赖&#xff08;2&#xff09;添加 bootstrap.yml&#xff08;4&#xff09;在 nacos 中添加配置 二、配置热更新1、配置热…...

重发布与路由策略

华子目录 重发布重发布条件重发布配置规则重发布名词配置命令ospf往rip重发布&#xff08;重发布动态&#xff09;静态往rip重发布&#xff08;重发布静态&#xff09;直连往rip重发布&#xff08;重发布直连&#xff09;rip往ospf重发布&#xff08;重发布动态&#xff09;静态…...

57. 插入区间(C++题解)

57. 插入区间 插入区间 给你一个无重叠的 &#xff0c;按照区间起始端点排序的区间列表。 在列表中插入一个新的区间&#xff0c;你需要确保列表中的区间仍然有序且不重叠&#xff08;如果有必要的话&#xff0c;可以合并区间&#xff09;。 示例 1&#xff1a; 输入&#x…...

【数据结构Java版】 初识泛型和包装类

目录 1.包装类 1.1基本数据类型以及它们所对应的包装类 1.2装箱和拆箱 1.3自动装箱和自动拆箱 2.什么是泛型 3.引出泛型 4.泛型类的使用 4.1语法 4.2示例 4.3类型推导 5.泛型是如何编译的 5.1擦除机制 5.2正确的写法 6.泛型的上届 6.1语法 6.2示例 …...

Spring中如何解决循环依赖问题的三种方法

什么是循环依赖问题 在 Spring 中&#xff0c;循环依赖问题指的是两个或多个 bean 之间相互依赖形成的闭环。具体而言&#xff0c;当 bean A 依赖于 bean B&#xff0c;同时 bean B 也依赖于 bean A&#xff0c;就形成了循环依赖。 循环依赖问题在 Spring 容器中是一个非常常…...

【ArcGIS Pro二次开发】(65):进出平衡SHP转TXT、TXT转SHP

最近一个小伙伴提了这么一个需求&#xff0c;需要把TXT和SHP进行互转。 这种TXT文件其实遇到了好几个版本&#xff0c;都有一点小差异。之前已经做过一个TXT转SHP的工具&#xff0c;但好像不适用。于是针对这个版本&#xff0c;做了互转的2个工具。 【SHP转TXT】 一、要实现的…...

Shell开发实践:服务器的磁盘、CPU、内存的占用监控

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师…...

超详细 async和await 项目实战运用(附加文字解答+源码)

文章目录 问题描述async什么是 asyncasync 的作用async 的应用场景async 优点 await什么是 awaitawait 的作用await 的应用场景await 的优点async和 await结合使用 结束语 大家好&#xff01;又到了愉快的周末假期&#xff0c;今天是2023年9月3日|农历七月十九&#xff0c;我最…...

Maven入门教程(三):Maven语法

视频教程&#xff1a;Maven保姆级教程 Maven入门教程(一)&#xff1a;安装Maven环境 Maven入门教程(二)&#xff1a;idea/Eclipse使用Maven Maven入门教程(三)&#xff1a;Maven语法 Maven入门教程(四)&#xff1a;Nexus私服 Maven入门教程(五)&#xff1a;自定义脚手架 6.Mav…...

C++技术点,故事解析

语言的魅力 从人类诞生开始 &#xff0c;南方古猿到现代人类经历了非常多变化&#xff1b; 南方古猿到能人 有什么变化&#xff1f; 能人会使用工具&#xff0c;由于会使用工具 就可以获得肉类食物&#xff0c;当然只能吃一些动物腐肉 直到进化成直立人的晚期&#xff0c;在东…...

数据结构(Java实现)-字符串常量池与通配符

字符串常量池 在Java程序中&#xff0c;类似于&#xff1a;1&#xff0c; 2&#xff0c; 3&#xff0c;3.14&#xff0c;“hello”等字面类型的常量经常频繁使用&#xff0c;为了使程序的运行速度更快、更节省内存&#xff0c;Java为8种基本数据类型和String类都提供了常量池。…...

python强化学习--gym安装与使用

最近开始学习强化学习&#xff0c;第一步肯定是要学会安装和使用pym&#xff0c;原本以为很简单&#xff0c;事实上确实很简单&#xff0c;但是遇到一个小问题&#xff0c;就是安装gym之后&#xff0c;在应用的过程中&#xff0c;游戏界面没有显示出来&#xff0c;了解后才知道…...

105. 从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 思路&#xff1a;题目给出了先序遍历和中序遍历的结果&#xff0c;因为先序遍历遵循根–>左–>…...

(第六天)初识Spring框架-SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录

SSM框架的学习与应用(Spring Spring MVC MyBatis)-Java EE企业级应用开发学习记录&#xff08;第六天&#xff09;初识Spring框架 ​ 昨天我们已经把Mybatis框架的基本知识全部学完&#xff0c;内容有Mybatis是一个半自动化的持久层ORM框架&#xff0c;深入学习编写动态SQL&a…...

如何使用『Nginx』配置后端『HTTPS』协议访问

前言 本篇博客主要讲解如何使用 Nginx 部署后端应用接口 SSL 证书&#xff0c;从而实现 HTTPS 协议访问接口&#xff08;本文使用公网 IP 部署&#xff0c;读者可以自行替换为域名&#xff09; 申请证书 须知 请在您的云服务平台申请 SSL 证书&#xff0c;一般来说证书期限…...

Git仓库简介

1、工作区、暂存区、仓库 工作区&#xff1a;电脑里能看到的目录。 暂存区&#xff1a;工作区有一个隐藏目录.git&#xff0c;是Git的版本库&#xff0c;Git的版本库里存了很多东西&#xff0c;其中最重要的就是称为stage&#xff08;或者叫index&#xff09;的暂存区&#xf…...

TensorRTC++ | INT8量化

Int8量化步骤 // 这是基本需要的组件 auto builder = make_nvshared(nvinfer1::createInferBuilder(logger)); auto config = make_nvshared(builder->createBuilderConfig())...

VS + qt环境使用QCustomPlot等三方库如何配置

文章目录 前言VS环境下引入第三方类库QCustomPlot方法一&#xff1a;解决办法&#xff1a; C中.dll与.lib文件的生成与使用1. 两种库&#xff1a;2.两种文件的区别 前言 Qt提供了显式和隐式导入第三方库方法&#xff0c;本文只介绍显示导入方法。 一般的第三方提供的库文件包…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

2025季度云服务器排行榜

在全球云服务器市场&#xff0c;各厂商的排名和地位并非一成不变&#xff0c;而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势&#xff0c;对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析&#xff1a; 一、全球“三巨头”…...

StarRocks 全面向量化执行引擎深度解析

StarRocks 全面向量化执行引擎深度解析 StarRocks 的向量化执行引擎是其高性能的核心设计&#xff0c;相比传统行式处理引擎&#xff08;如MySQL&#xff09;&#xff0c;性能可提升 5-10倍。以下是分层拆解&#xff1a; 1. 向量化 vs 传统行式处理 维度行式处理向量化处理数…...

英国云服务器上安装宝塔面板(BT Panel)

在英国云服务器上安装宝塔面板&#xff08;BT Panel&#xff09; 是完全可行的&#xff0c;尤其适合需要远程管理Linux服务器、快速部署网站、数据库、FTP、SSL证书等服务的用户。宝塔面板以其可视化操作界面和强大的功能广受国内用户欢迎&#xff0c;虽然官方主要面向中国大陆…...

湖北理元理律师事务所:债务清偿方案中的法律技术革新

文/金融法律研究组 当前债务服务市场存在结构性矛盾&#xff1a;债权人追求快速回款&#xff0c;债务人需要喘息空间。湖北理元理律师事务所通过创新法律技术&#xff0c;在《企业破产法》《民法典》框架下构建梯度清偿模型&#xff0c;实现多方利益平衡。 一、个人债务优化的…...

暴雨新专利解决服务器噪音与性能悖论

6月1日&#xff0c;我国首部数据中心绿色化评价方面国家标准《绿色数据中心评价》正式实施&#xff0c;为我国数据中心的绿色低碳建设提供了明确指引。《评价》首次将噪音控制纳入国家级绿色评价体系&#xff0c;要求从设计隔声结构到运维定期监测实现闭环管控&#xff0c;加速…...