Linux内核AES加解密模块问题:解密结果与明文不一致
我之前在内核里开发AES加解密模块时,也碰到过一模一样的情况——所有API都返回成功,但解密出来的结果就是和明文对不上。结合你的日志和常见的内核AES坑,给你几个最可能的排查方向:
1. 初始化向量(IV)的一致性问题
如果你的AES用的是CBC、CFB这类需要IV的模式,加密和解密必须使用完全相同的初始IV。内核里的crypto_cipher上下文在加密过程中会自动更新IV(用当前块的密文作为下一块的IV),加密完成后上下文里的IV已经不是初始值了。如果解密时直接复用这个被修改过的IV,前16字节(AES块大小)的解密结果肯定会出错,后续块也会跟着乱。
正确的做法是:加密前先把初始IV备份一份,解密前将备份的IV重新设置到解密上下文(或者同一个上下文)中。
2. 算法模式与参数不匹配
确认加密和解密使用的是完全相同的算法模式:比如加密用的是"aes-cbc",解密就不能用"aes-ecb";密钥长度也要对应(128/192/256位),虽然crypto_cipher_setkey返回成功,但如果密钥长度和算法要求不匹配,某些内核版本可能不会报错但会产生错误的加解密结果。
另外,要确保你分配的cipher handle是正确的,比如:
struct crypto_cipher *tfm = crypto_alloc_cipher("aes-cbc", 0, 0);
3. 内存对齐问题
很多内核AES实现(尤其是使用硬件加速的版本)要求明文、密文、密钥的内存地址是16字节对齐的。如果你的数据没有对齐,虽然API会返回成功,但底层的加解密操作会出现隐式错误,导致结果不符。
解决方法是在声明数据时加上__aligned(16)属性:
u8 plaintext[16] __aligned(16); u8 ciphertext[16] __aligned(16); u8 key[16] __aligned(16);
4. 数据长度与填充问题
AES是块加密算法,每块固定16字节。如果你的明文长度不是16的整数倍,需要手动做填充(比如PKCS#7填充),内核的AES API本身不会自动处理填充。从你的日志看,明文刚好是16字节,这一点没问题,但如果后续处理多块数据,一定要确保加密和解密的数据长度完全一致,且填充/去填充逻辑正确。
另外,检查解密时传入的密文是否完整——你的日志里解密部分的密文被截断了(decrypting -> encrypted: 91 1c 91 51 3c 22 77 dd 82 b4 b7 52 c7 9...),要确认解密时传入的密文长度和加密后的密文长度完全相同。
5. 上下文的正确复用
如果你复用同一个crypto_cipher上下文做加解密,一定要确保每次操作前重置必要的参数(比如IV),并且重新设置密钥(如果有修改的话)。不要假设上下文会自动恢复到初始状态,加密后的上下文状态已经被修改,直接用来解密会导致错误。
参考示例代码
这里给你一个完整的内核AES-CBC加解密示例,涵盖了上面提到的所有注意点:
#include <linux/crypto.h> #include <linux/module.h> #include <linux/string.h> static int __init aes_test_init(void) { struct crypto_cipher *tfm; // 128位密钥 u8 key[16] __aligned(16) = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; // 初始IV u8 iv[16] __aligned(16) = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}; u8 iv_backup[16] __aligned(16); // 你的明文 u8 plaintext[16] __aligned(16) = {0xbb, 0x2b, 0xd7, 0xfb, 0xfc, 0xc6, 0x0c, 0xf6, 0x82, 0x92, 0xcd, 0xe1, 0x62, 0x2f, 0x9f, 0x95}; u8 ciphertext[16] __aligned(16); u8 decrypted[16] __aligned(16); int ret; // 分配CBC模式的AES cipher handle tfm = crypto_alloc_cipher("aes-cbc", 0, 0); if (IS_ERR(tfm)) { pr_err("Failed to allocate AES-CBC cipher handle\n"); return PTR_ERR(tfm); } // 设置密钥 ret = crypto_cipher_setkey(tfm, key, sizeof(key)); if (ret) { pr_err("Failed to set AES key: %d\n", ret); goto free_tfm; } // 备份初始IV memcpy(iv_backup, iv, sizeof(iv)); // 加密:注意crypto_cipher_encrypt_one会修改IV,所以要传入正确的初始IV memcpy(ciphertext, plaintext, sizeof(plaintext)); crypto_cipher_encrypt_one(tfm, ciphertext, plaintext); pr_info("Encrypted: "); for (int i = 0; i < 16; i++) { pr_cont("%02x ", ciphertext[i]); } pr_cont("\n"); // 重置IV为初始值,准备解密 memcpy(iv, iv_backup, sizeof(iv)); // 解密 memcpy(decrypted, ciphertext, sizeof(ciphertext)); crypto_cipher_decrypt_one(tfm, decrypted, ciphertext); pr_info("Decrypted: "); for (int i = 0; i < 16; i++) { pr_cont("%02x ", decrypted[i]); } pr_cont("\n"); // 验证结果 if (memcmp(decrypted, plaintext, sizeof(plaintext)) == 0) { pr_info("AES decryption matched plaintext!\n"); ret = 0; } else { pr_err("Decryption result mismatch!\n"); ret = -EINVAL; } free_tfm: crypto_free_cipher(tfm); return ret; } static void __exit aes_test_exit(void) { pr_info("AES test module exited\n"); } module_init(aes_test_init); module_exit(aes_test_exit); MODULE_LICENSE("GPL");
你可以对比自己的代码,看看有没有遗漏上面提到的细节,尤其是IV的备份和重置、内存对齐这两点,大概率是问题所在。
内容的提问来源于stack exchange,提问作者avee137

