OpenSSL AES加密踩坑记:CFB128模式加解密竟然用同一个Key?函数参数num到底怎么设?

张开发
2026/4/21 14:32:58 15 分钟阅读

分享文章

OpenSSL AES加密踩坑记:CFB128模式加解密竟然用同一个Key?函数参数num到底怎么设?
OpenSSL AES-CFB128模式实战解密为什么加密和解密要用同一个Key第一次用OpenSSL实现AES-CFB128模式加密时我遇到了两个让人抓狂的问题为什么解密时不能用解密Key为什么num参数必须设为0如果你也在为这两个问题头疼这篇文章就是为你准备的。我们将从实际代码出发深入解析CFB128模式的独特之处帮你避开这些坑。1. CFB128模式的核心特点AES-CFB(Cipher Feedback)模式是一种将分组密码转换为流密码的工作模式。与常见的ECB、CBC模式不同CFB模式有几个关键特性自同步流密码即使部分密文丢失后续数据仍能正确解密无需填充可以处理任意长度的数据不像ECB/CBC需要对齐块大小加密解密同Key这是最反直觉的一点解密时也需要使用加密Key// 典型错误试图在解密时使用解密Key AES_set_decrypt_key(key, 128, aes_key); // 这行在CFB模式下会导致解密失败在大多数AES模式中我们习惯性地认为加密用加密Key解密用解密Key。但CFB模式打破了这一常规。让我们通过一个对比表格来理解不同模式下的Key使用差异模式加密Key解密Key是否需要填充ECB加密Key解密Key是CBC加密Key解密Key是CFB加密Key加密Key否OFB加密Key加密Key否关键提示CFB模式下无论加密还是解密都必须使用AES_set_encrypt_key生成的Key2. num参数的神秘面纱AES_cfb128_encrypt函数中的num参数可能是最让人困惑的部分。官方文档只说应该设为0但没解释为什么。经过多次测试和源码分析我发现当num不为0时OpenSSL内部会触发断言失败这个参数实际上是为了支持CFB8/CFB1等变体而保留的在CFB128模式下它必须保持为0// 正确用法 int num 0; // 必须初始化为0 AES_cfb128_encrypt(input, output, length, key, ivec, num, enc);为什么会有这个限制深入OpenSSL源码后我发现CFB128模式内部将整个128位块作为处理单元而num参数在CFB8/CFB1模式下用于跟踪位偏移。在CFB128中这个跟踪机制不适用因此必须设为0。3. IV初始向量的正确管理CFB模式对初始向量(IV)的处理也有特殊要求。与CBC模式不同CFB的IV会被加密函数修改不能立即重用需要为每次加密/解密操作创建副本// 错误示范直接使用原始IV unsigned char iv[16] {...}; AES_cfb128_encrypt(data1, out1, len, key, iv, num, AES_ENCRYPT); AES_cfb128_encrypt(data2, out2, len, key, iv, num, AES_ENCRYPT); // 这里iv已被修改 // 正确做法每次使用前复制IV unsigned char iv[16] {...}; unsigned char temp_iv[16]; memcpy(temp_iv, iv, 16); AES_cfb128_encrypt(data1, out1, len, key, temp_iv, num, AES_ENCRYPT); memcpy(temp_iv, iv, 16); // 再次复制原始IV AES_cfb128_encrypt(data2, out2, len, key, temp_iv, num, AES_ENCRYPT);4. 完整实现与验证下面是一个经过实战检验的CFB128实现方案包含了所有关键点#include openssl/aes.h #include string.h int aes_cfb128_encrypt(const unsigned char *input, int input_len, unsigned char *output, const unsigned char *key, int key_bits, const unsigned char *iv) { AES_KEY aes_key; int num 0; unsigned char ivec[AES_BLOCK_SIZE]; // 必须使用加密Key即使是在解密时 if(AES_set_encrypt_key(key, key_bits, aes_key) 0) { return -1; } memcpy(ivec, iv, AES_BLOCK_SIZE); AES_cfb128_encrypt(input, output, input_len, aes_key, ivec, num, AES_ENCRYPT); return 0; } int aes_cfb128_decrypt(const unsigned char *input, int input_len, unsigned char *output, const unsigned char *key, int key_bits, const unsigned char *iv) { AES_KEY aes_key; int num 0; unsigned char ivec[AES_BLOCK_SIZE]; // 注意解密也使用加密Key if(AES_set_encrypt_key(key, key_bits, aes_key) 0) { return -1; } memcpy(ivec, iv, AES_BLOCK_SIZE); AES_cfb128_encrypt(input, output, input_len, aes_key, ivec, num, AES_DECRYPT); return 0; }验证这个实现的正确性很简单加密一段数据用相同的Key和IV解密比较解密结果与原始数据// 测试代码示例 void test_cfb128() { unsigned char key[] 0123456789abcdef; unsigned char iv[] 1234567890abcdef; unsigned char plaintext[] Hello, CFB128 mode!; unsigned char ciphertext[sizeof(plaintext)]; unsigned char decrypted[sizeof(plaintext)]; // 加密 aes_cfb128_encrypt(plaintext, strlen((char*)plaintext), ciphertext, key, 128, iv); // 重置IV unsigned char new_iv[] 1234567890abcdef; // 解密 aes_cfb128_decrypt(ciphertext, strlen((char*)plaintext), decrypted, key, 128, new_iv); // 验证 if(memcmp(plaintext, decrypted, strlen((char*)plaintext)) 0) { printf(CFB128 test passed!\n); } else { printf(CFB128 test failed!\n); } }在实际项目中我遇到过因为忽略这些细节导致的加密/解密不匹配问题。特别是在长时间运行的系统中IV管理不当会导致前几个块解密失败。记住每次加密/解密操作都需要使用原始的、未被修改的IV。

更多文章