【AES加密专题】7.AES全局函数的编写
目录
1.AES初始化
2.加密函数
3.解密函数
4.测试
AES加密专题:
【AES加密专题】1.AES的原理详解和加密过程-CSDN博客
【AES加密专题】2.AES头文件详解-CSDN博客
【AES加密专题】3.工具函数的编写(1)-CSDN博客
【AES加密专题】4.Sbox的解析和生成-CSDN博客
【AES加密专题】5.功能函数的编写(2)-CSDN博客
【AES加密专题】6.功能函数的编写(3)-CSDN博客
【AES加密专题】7.AES全局函数的编写-CSDN博客
【AES加密专题】8.实战-测试加密网站和代码-CSDN博客
1.AES初始化
根据提供的原始密钥生成轮密钥表,用于后续的AES加密和解密操作。
/*** @brief AES密钥扩展初始化(生成轮密钥表)* * 功能说明:根据输入的原始密钥(16/24/32字节,对应128/192/256位),按照AES标准扩展生成轮密钥表,* 供后续加密(block_encrypt)和解密(block_decrypt)使用。轮密钥表大小为 Nb*(Nr+1) 字(每字4字节),* 包含初始密钥和所有轮的子密钥。* * @param[in] pKey 指向原始密钥的指针:* - 128位密钥:16字节(4字),Nk=4,Nr=10;* - 192位密钥:24字节(6字),Nk=6,Nr=12;* - 256位密钥:32字节(8字),Nk=8,Nr=14;* 需确保密钥长度正确,函数不做长度检查。* * @note * 1. 轮密钥表生成逻辑:* - 第一步:将原始密钥直接复制到轮密钥表的前Nk个字(起始部分);* - 第二步:从第Nk个字开始,通过“前序密钥字+特殊变换(旋转、S-盒替换、轮常量异或)”生成后续密钥字;* 2. 特殊变换适用场景:* - 每生成Nk个字(i%Nk==0):需执行“旋转字→S-盒替换→轮常量异或”;* - 当Nk>6且i%Nk==Nb(仅256位密钥):额外执行一次S-盒替换;* 3. 轮常量(Rcon):用于打破密钥扩展的对称性,初始值0x01000000,每次更新时首字节乘2(GF(2^8)运算)。*/
void aes_init(const void *pKey)
{unsigned char i; // 循环索引:表示当前生成的密钥字序号(从Nk到Nb*(Nr+1)-1)unsigned char *pRoundKey; // 指针:指向当前待生成的密钥字(4字节为1字)unsigned char Rcon[4] = {0x01, 0x00, 0x00, 0x00}; // 轮常量(初始值:0x01000000,仅首字节有效)// -------------------------- 步骤1:复制原始密钥到轮密钥表起始部分 --------------------------// 原始密钥共Nk个字(每字4字节),直接复制到g_roundKeyTable的前Nk*4字节memcpy(g_roundKeyTable, pKey, 4 * Nk);// -------------------------- 步骤2:扩展生成剩余的轮密钥字 --------------------------pRoundKey = &g_roundKeyTable[4 * Nk]; // 指针定位到第Nk个字(第一个待生成的密钥字)// 循环生成从第Nk个字到第Nb*(Nr+1)-1个字(轮密钥表总字数为Nb*(Nr+1))for (i = Nk; i < Nb * (Nr + 1); pRoundKey += 4, i++) // 每次生成1个字(4字节),指针后移4字节{// 2.1 基础:复制前一个密钥字作为初始值memcpy(pRoundKey, pRoundKey - 4, 4);// 2.2 特殊处理1:每Nk个字(i%Nk==0),执行“旋转→S-盒替换→轮常量异或”if (i % Nk == 0){rotation_word(pRoundKey); // 旋转字:将4字节字左移1字节(如[w0,w1,w2,w3]→[w1,w2,w3,w0])sub_bytes(pRoundKey, 4, 0); // S-盒替换:对旋转后的字执行字节替换(加密模式,bInvert=0)xor_bytes(pRoundKey, Rcon, 4);// 轮常量异或:与当前轮常量Rcon异或,增强密钥扩散性// 更新轮常量:首字节乘2(GF(2^8)运算),后续轮次使用(其他字节保持0)Rcon[0] = gf_mult_by02(Rcon[0]);}// 2.3 特殊处理2:当Nk>6(仅256位密钥,Nk=8)且i%Nk==Nb(Nb=4),额外执行S-盒替换else if (Nk > 6 && i % Nk == Nb){sub_bytes(pRoundKey, 4, 0); // 对当前字执行S-盒替换,增强256位密钥的安全性}// 2.4 最终生成:与前Nk个密钥字异或,得到当前密钥字(核心扩散步骤)xor_bytes(pRoundKey, pRoundKey - 4 * Nk, 4);}
}
2.加密函数
AES加密函数aes_encrypt,能够在ECB或CBC模式下加密输入的明文数据。
/*** @brief AES加密函数(支持ECB/CBC模式)* * 功能说明:对输入的明文数据按块加密,输出密文。支持两种工作模式:* - ECB模式(电子密码本):每个16字节数据块独立加密,无需初始化向量(IV),安全性较低;* - CBC模式(密码分组链接):每个数据块加密前需与前一个密文块(或初始IV)异或,增强安全性,需提供16字节IV。* 数据长度必须是16字节的整数倍(AES固定块大小),支持“原地加密”(明文与密文指针相同)。* * @param[in] pPlainText 指向明文数据的指针:待加密的原始数据,长度为nDataLen字节。* @param[out] pCipherText 指向密文数据的指针:存储加密结果,允许与pPlainText相同(原地加密),需确保有nDataLen字节空间。* @param[in] nDataLen 数据总长度(字节):必须是16的整数倍(4*Nb=16,AES块大小),否则加密不完整。* @param[in] pIV 初始化向量(仅CBC模式需要):指向16字节的IV,加密首块时与明文异或,后续块与前序密文异或;* ECB模式可传入NULL(无实际作用)。* * @note 1. 模式切换:通过宏AES_MODE控制(AES_MODE_ECB或AES_MODE_CBC),编译前需定义;* 2. IV要求:CBC模式下pIV必须指向16字节有效内存,且加密/解密需使用相同IV,否则密文无法正确还原;* 3. 安全性提示:ECB模式因块独立加密,相同明文块会生成相同密文块,不推荐用于实际场景,优先使用CBC模式;* 4. 依赖前提:需先通过aes_init函数初始化轮密钥表(否则block_encrypt无法正常工作)。*/
void AES_Encrypt(const unsigned char *pPlainText, unsigned char *pCipherText,unsigned int nDataLen, const unsigned char *pIV)
{unsigned int i; // 循环索引:表示待加密的块数量(nDataLen / 16)// -------------------------- 步骤1:准备加密缓冲区(支持原地加密) --------------------------// 若明文与密文指针不同,先将明文复制到密文缓冲区(后续直接在密文缓冲区操作)if (pPlainText != pCipherText){memcpy(pCipherText, pPlainText, nDataLen);}// 若指针相同(原地加密),直接使用pCipherText作为操作缓冲区(覆盖原始明文)// -------------------------- 步骤2:分块加密(每块16字节) --------------------------// 计算总块数:nDataLen / 16(因nDataLen是16的整数倍),循环从块数递减到1for (i = nDataLen / (4 * Nb); i > 0; i--, pCipherText += 4 * Nb){// 2.1 CBC模式:当前块与前序密文(首块与IV)异或(引入块间依赖)#if AES_MODE == AES_MODE_CBC// 密文缓冲区当前块 ^ IV(或前序密文块)xor_bytes(pCipherText, pIV, 4 * Nb); #endif// 2.2 执行单块核心加密(AES轮操作:字节替换→行移位→列混合→轮密钥加)block_encrypt(pCipherText);// 2.3 更新IV(仅CBC模式):下一块的IV为当前块的密文(形成链接)pIV = pCipherText;}
}
3.解密函数
AES解密函数aes_decrypt,支持在ECB和CBC模式下对输入的密文数据进行解密。
/*** @brief AES解密函数(支持ECB/CBC模式)* * 功能说明:对输入的密文数据按块解密,输出明文。支持两种工作模式,逻辑与加密对称:* - ECB模式:每个16字节密文块独立解密,无需IV,与加密块对应;* - CBC模式:需先解密当前块,再与前一个密文块(首块与IV)异或还原明文,需与加密相同的16字节IV。* 数据长度必须是16字节的整数倍,支持“原地解密”(密文与明文指针相同),核心优化是“逆序解密”——* 从最后一个块开始处理,避免额外空间保存前序密文块(CBC模式关键)。* * @param[in] pCipherText 指向密文数据的指针:待解密的密文,长度为nDataLen字节。* @param[out] pPlainText 指向明文数据的指针:存储解密结果,允许与pCipherText相同(原地解密),需确保有nDataLen字节空间。* @param[in] nDataLen 数据总长度(字节):必须是16的整数倍(4*Nb=16),否则解密不完整。* @param[in] pIV 初始化向量(仅CBC模式需要):指向16字节的IV,需与加密时使用的IV完全一致;* ECB模式可传入NULL(无实际作用)。* * @note 1. 模式对称:CBC模式解密流程与加密相反——加密是“明文^IV→加密”,解密是“解密→密文^IV”;* 2. 逆序解密原因:CBC模式下,当前明文需与“前一个密文块”异或,逆序处理可避免覆盖未使用的密文块(无需额外空间);* 3. 安全性:同加密,ECB模式不推荐用于实际场景,优先使用CBC模式,且IV需随机且与加密一致;* 4. 依赖前提:需先通过aes_init函数初始化轮密钥表(block_decrypt依赖轮密钥)。*/
void AES_Decrypt(const unsigned char *pCipherText, unsigned char *pPlainText,unsigned int nDataLen, const unsigned char *pIV)
{unsigned int i; // 循环索引:表示待解密的块数量(nDataLen / 16)// -------------------------- 步骤1:准备解密缓冲区(支持原地解密) --------------------------// 若密文与明文指针不同,先将密文复制到明文缓冲区(后续直接在缓冲区操作,避免破坏原密文)if (pPlainText != pCipherText){memcpy(pPlainText, pCipherText, nDataLen);}// 若指针相同(原地解密):直接在密文地址操作,解密后覆盖原始密文// -------------------------- 步骤2:逆序分块解密(从最后一块到第一块) --------------------------// 1. 指针定位:将pPlainText指向最后一个块(地址=起始地址 + 总长度 - 块大小)pPlainText += nDataLen - 4 * Nb;// 2. 循环解密:块数量 = nDataLen / 16,从块数递减到1(逆序处理)for (i = nDataLen / (4 * Nb); i > 0; i--, pPlainText -= 4 * Nb){// 2.1 执行单块核心解密(AES逆轮操作:逆行移位→逆字节替换→轮密钥加→非首轮逆列混淆)block_decrypt(pPlainText);// 2.2 CBC模式:解密后的数据块需与“前序密文块”(首块与IV)异或,还原明文#if AES_MODE == AES_MODE_CBCif (i == 1){// 最后处理的是“第一块密文”:解密后与IV异或(加密时第一块明文^IV后加密)xor_bytes(pPlainText, pIV, 4 * Nb);}else{// 非第一块:解密后与“前一个密文块”异或(前一个密文块未被覆盖,地址=当前块地址 - 块大小)xor_bytes(pPlainText, pPlainText - 4 * Nb, 4 * Nb);}#endif}
}
4.测试
/*** @brief 打印字节数组的十六进制形式* * 功能:将输入的字节数组以十六进制格式逐字节打印,每个字节间用空格分隔,* 便于查看加密后的密文(二进制数据),避免乱码。* * @param data 指向待打印的字节数组(如密文、密钥、IV)* @param length 字节数组的长度(需打印的字节数)*/
void print_hex(uint8_t *data, size_t length) {// 逐字节打印十六进制,%02x确保不足2位补0,加空格分隔增强可读性for (size_t i = 0; i < length; i++) {printf("%02x ", data[i]);}printf("\n"); // 所有字节打印完后换行,避免后续输出连在一起
}/*** @brief AES加密/解密测试函数* * 功能:通过一个完整流程测试AES工作:* 1. 定义测试明文、128位密钥、CBC模式IV;* 2. 初始化AES(扩展密钥生成轮密钥表);* 3. 对明文加密,得到密文;* 4. 对密文解密,还原明文;* 5. 打印密文(十六进制)和解密后的明文(字符串),验证正确性。*/
void test(void) {// 1. 测试数据初始化(注意:AES处理固定16字节块,此处明文长度为16字节)uint8_t plaintext[16] = "11aa22bb33cc44dd"; // 待加密明文(16字节,无字符串结束符,避免溢出)uint8_t ciphertext[16]; // 存储加密后的密文(16字节)uint8_t decryptedtext[17]; // 存储解密后的明文(17字节:16字节数据+1字节字符串结束符)// AES128密钥(128位=16字节,字符串"123456789abcdefa"共16个字符,无结束符)uint8_t aes128_key[16] = "123456789abcdefa";// CBC模式初始化向量(IV需16字节,与加密/解密共享,字符串"0102030405123456"共16个字符)uint8_t aes_iv[16] = "0102030405123456";// 2. AES初始化:通过原始密钥生成轮密钥表(供后续加密/解密使用)aes_init(aes128_key);printf("AES初始化完成(128位密钥)\n");// 3. 执行加密:明文→密文(CBC模式,需传入IV)// 注意:sizeof(plaintext)=16,与AES块大小一致,无需补全aes_encrypt(plaintext, ciphertext, sizeof(plaintext), aes_iv);printf("加密后的密文(十六进制):");print_hex(ciphertext, sizeof(ciphertext)); // 打印密文(二进制数据需用十六进制查看)// 4. 执行解密:密文→明文(CBC模式,需传入与加密相同的IV)aes_decrypt(ciphertext, decryptedtext, sizeof(ciphertext), aes_iv);// 给解密后的明文加字符串结束符(避免printf("%s")读取越界)decryptedtext[sizeof(plaintext)] = '\0';// 5. 打印解密结果,验证是否与原始明文一致printf("解密后的明文(字符串):%s\n", decryptedtext);// 对比原始明文与解密后明文,确认正确性if (memcmp(plaintext, decryptedtext, sizeof(plaintext)) == 0) {printf("测试成功:解密后的明文与原始明文一致!\n");} else {printf("测试失败:解密后的明文与原始明文不一致!\n");}
}// (可选)主函数:调用测试函数
// int main(void) {
// test();
// return 0;
// }
AES加密后的数据一般不是可打印字符,因此直接使用printf("%s",saveBuf);输出加密后的数据会出现乱码。解决方法是先以十六进制形式输出,或者将数据编码为Base64,以便于字符显示。