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=Wi−4⊕Wi−1。
如果索引是4的倍数,则 W i = W i − 4 ⊕ T ( W i − 1 ) W_i=W_{i-4} \oplus T(W_{i-1}) Wi=Wi−4⊕T(Wi−1)。其中T函数包括:
①字循环:假设 W i − 1 W_{i-1} Wi−1从上到下为 [ 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(Wi−1)
这里简单分析一下计算索引为4的倍数的这一行:tempKey[0][0] ^= S[tempKey[uKeyColumns-1][1]],其中tempKey[uKeyColumns-1][1]即 W i − 1 W_{i-1} Wi−1在字循环后的第一个元素。S[tempKey[uKeyColumns-1][1]]则是进行了S盒字节代换,S盒实际上是一个uint8_t S[256]的数组。tempKey[0][0]就是 W i − 4 W_{i-4} Wi−4,异或的结果保存在tempKey[0][0]中。
从代码中来看,只有第一个元素,即tempKey[0][0],需要异或轮常量rcon(round constant)。所以理论和实际实现有一定的差异,当然你也可以全部都异或,不同人写的代码在它的实现细节可能会有一些不同。和CRC的计算一样,你还可以有一些CRC反转的操作。对于AES来说,我们只需要保证加密和解密遵循同一个规则就行了。
1.3 blockEncrypt:加密多个块
在前面定义的AES类中的加解密函数有blockEncrypt、padEncrypt、blockDecrypt和padDecrypt。对于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)文中的T1、T2、T3和T4都是static uint8_t Tn[256][4]的数组,它们作为加密过程中每一列的S盒,而没有使用前面生成轮密钥的S盒,这样是为了增加加密的复杂性。
(2)在上一节文章中有介绍,添加轮密钥后,进行字节替换,然后才是行移位。但在上面的代码中,先进行了行移位,再使用T1~T4四个S盒进行了字节替换。
(3)上面的代码中没有列混合变换,这是因为矩阵的乘法很耗时间,为了追求速度,这里就省略了。
(4)上面代码中的第九轮与前面的几轮又有些许的不同,之前都是直接在T1到T4的表中替换,将每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 a⊕b⊕a=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基础知识和计算过程中,大概介绍了AES(Rijndael)加密的整个过程。那么在这一篇文章中,就来看一下AES在代码中是如何实现的,也有助于我们理解其中的一些细节。 本篇文章所用的AES代码来源于Szymon Stefanek的开源C代码 文章…...
SpringBoot项目通过分词器生成词云
目录 前言一、词云是什么?二、使用步骤1.引入依赖2.application.yml3.Controller4.分词工具类4.词云生成工具类、支持输出文件和字节流 注意 前言 公司项目涉及到员工任务管理,需要从员工任务中获取任务信息生成个人词云图,可以把员工任务中…...
Nacos 配置管理及相关使用
文章目录 Nacos 配置管理一、统一配置管理1、在Nacos 中添加配置文件2、从微服务拉取配置3、配置实现步骤(1)引入 nacos-config 依赖(2)添加 bootstrap.yml(4)在 nacos 中添加配置 二、配置热更新1、配置热…...
重发布与路由策略
华子目录 重发布重发布条件重发布配置规则重发布名词配置命令ospf往rip重发布(重发布动态)静态往rip重发布(重发布静态)直连往rip重发布(重发布直连)rip往ospf重发布(重发布动态)静态…...
57. 插入区间(C++题解)
57. 插入区间 插入区间 给你一个无重叠的 ,按照区间起始端点排序的区间列表。 在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。 示例 1: 输入&#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 中,循环依赖问题指的是两个或多个 bean 之间相互依赖形成的闭环。具体而言,当 bean A 依赖于 bean B,同时 bean B 也依赖于 bean A,就形成了循环依赖。 循环依赖问题在 Spring 容器中是一个非常常…...
【ArcGIS Pro二次开发】(65):进出平衡SHP转TXT、TXT转SHP
最近一个小伙伴提了这么一个需求,需要把TXT和SHP进行互转。 这种TXT文件其实遇到了好几个版本,都有一点小差异。之前已经做过一个TXT转SHP的工具,但好像不适用。于是针对这个版本,做了互转的2个工具。 【SHP转TXT】 一、要实现的…...
Shell开发实践:服务器的磁盘、CPU、内存的占用监控
🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年6月CSDN上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师…...
超详细 async和await 项目实战运用(附加文字解答+源码)
文章目录 问题描述async什么是 asyncasync 的作用async 的应用场景async 优点 await什么是 awaitawait 的作用await 的应用场景await 的优点async和 await结合使用 结束语 大家好!又到了愉快的周末假期,今天是2023年9月3日|农历七月十九,我最…...
Maven入门教程(三):Maven语法
视频教程:Maven保姆级教程 Maven入门教程(一):安装Maven环境 Maven入门教程(二):idea/Eclipse使用Maven Maven入门教程(三):Maven语法 Maven入门教程(四):Nexus私服 Maven入门教程(五):自定义脚手架 6.Mav…...
C++技术点,故事解析
语言的魅力 从人类诞生开始 ,南方古猿到现代人类经历了非常多变化; 南方古猿到能人 有什么变化? 能人会使用工具,由于会使用工具 就可以获得肉类食物,当然只能吃一些动物腐肉 直到进化成直立人的晚期,在东…...
数据结构(Java实现)-字符串常量池与通配符
字符串常量池 在Java程序中,类似于:1, 2, 3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和String类都提供了常量池。…...
python强化学习--gym安装与使用
最近开始学习强化学习,第一步肯定是要学会安装和使用pym,原本以为很简单,事实上确实很简单,但是遇到一个小问题,就是安装gym之后,在应用的过程中,游戏界面没有显示出来,了解后才知道…...
105. 从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。 思路:题目给出了先序遍历和中序遍历的结果,因为先序遍历遵循根–>左–>…...
(第六天)初识Spring框架-SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录
SSM框架的学习与应用(Spring Spring MVC MyBatis)-Java EE企业级应用开发学习记录(第六天)初识Spring框架 昨天我们已经把Mybatis框架的基本知识全部学完,内容有Mybatis是一个半自动化的持久层ORM框架,深入学习编写动态SQL&a…...
如何使用『Nginx』配置后端『HTTPS』协议访问
前言 本篇博客主要讲解如何使用 Nginx 部署后端应用接口 SSL 证书,从而实现 HTTPS 协议访问接口(本文使用公网 IP 部署,读者可以自行替换为域名) 申请证书 须知 请在您的云服务平台申请 SSL 证书,一般来说证书期限…...
Git仓库简介
1、工作区、暂存区、仓库 工作区:电脑里能看到的目录。 暂存区:工作区有一个隐藏目录.git,是Git的版本库,Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区…...
TensorRTC++ | INT8量化
Int8量化步骤 // 这是基本需要的组件 auto builder = make_nvshared(nvinfer1::createInferBuilder(logger)); auto config = make_nvshared(builder->createBuilderConfig())...
VS + qt环境使用QCustomPlot等三方库如何配置
文章目录 前言VS环境下引入第三方类库QCustomPlot方法一:解决办法: C中.dll与.lib文件的生成与使用1. 两种库:2.两种文件的区别 前言 Qt提供了显式和隐式导入第三方库方法,本文只介绍显示导入方法。 一般的第三方提供的库文件包…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
链式法则中 复合函数的推导路径 多变量“信息传递路径”
非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y)) 来全面说明。我们会展示其全微分形式(偏导…...
