使用CryptoAPI的KP_IV加密后解密异常,如何随密文发送IV?
原错误代码
#include <iostream> #include <windows.h> #include <wincrypt.h> using std::string; const char *key = "yM7TbMuL3TNUmeBs1Mqdw73G2BmMBHHV7cDAF3V5v9FAOqYc0mF133T8WE5foqAG3rJAK8dU1hKc"; HCRYPTPROV hCryptProv = NULL; HCRYPTKEY hKey = NULL; #define AES_BLOCK_SIZE 16 BOOL AesInitialization() { // Set hCryptProv and hKey HCRYPTHASH hHash = NULL; BOOL result = FALSE; if (CryptAcquireContextW(&hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) if (CryptCreateHash(hCryptProv, CALG_SHA1, 0, 0, &hHash)) if (CryptHashData(hHash, (BYTE*)key, strlen(key), 0)) if (CryptDeriveKey(hCryptProv, CALG_AES_256, hHash, 0, &hKey)) { DWORD dwMode = CRYPT_MODE_CBC; result = CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&dwMode, 0); } if (hHash != NULL) CryptDestroyHash(hHash); return result; } BOOL AesEncrypt(string &data) { BOOL result = TRUE; string iv; iv.resize(AES_BLOCK_SIZE); // allocate space for random IV result = CryptGenRandom(hCryptProv, AES_BLOCK_SIZE, (BYTE *)&iv[0]); if (result) { if (CryptSetKeyParam(hKey, KP_IV, (BYTE *) iv.c_str(), 0)) { data = data + iv; // Append the random string DWORD dwDataLen = data.length(); data.resize(dwDataLen + AES_BLOCK_SIZE); // give enough space for padding result = CryptEncrypt(hKey, NULL, TRUE, 0, (BYTE *)&data[0], &dwDataLen, data.length()); data.resize(dwDataLen); // resize to actual ciphertext length } } return result; } BOOL AesDecrypt(string &data) { BOOL success = TRUE; string iv; iv.resize(AES_BLOCK_SIZE); // ensure IV has the correct size iv.assign(key); if (CryptSetKeyParam(hKey, KP_IV, (BYTE*)iv.c_str(), 0)) { if (CryptSetKeyParam(hKey, KP_IV, (BYTE*)&iv[0], 0)) { DWORD dwDataLen = data.length(); success = CryptDecrypt(hKey, NULL, TRUE, 0, (BYTE *)&data[0], &dwDataLen); data.resize(dwDataLen - AES_BLOCK_SIZE); // resize to actual ciphertext length } } return success; } int main() { if (!AesInitialization()) return 1; string message = "my mother & your mother were hanging up clothes"; string iv; if (!AesEncrypt(message)) return 1; if (!AesDecrypt(message)) return 1; return 0; }
错误分析
- 加密逻辑错误:将明文与IV拼接后再加密,导致IV被当作明文的一部分参与加密,而非作为CBC模式的初始化向量使用
- 解密逻辑错误:未从密文中提取加密时使用的随机IV,反而用密钥作为IV,完全不符合CBC模式的解密要求
修正方案
核心思路
- 加密时:先生成随机IV,用该IV加密明文,再将IV附加到密文末尾(或开头),形成最终传输数据
- 解密时:先从传输数据中提取IV,再用该IV解密剩余的密文内容
修正后的完整代码
#include <iostream> #include <windows.h> #include <wincrypt.h> using std::string; const char *key = "yM7TbMuL3TNUmeBs1Mqdw73G2BmMBHHV7cDAF3V5v9FAOqYc0mF133T8WE5foqAG3rJAK8dU1hKc"; HCRYPTPROV hCryptProv = NULL; HCRYPTKEY hKey = NULL; #define AES_BLOCK_SIZE 16 BOOL AesInitialization() { HCRYPTHASH hHash = NULL; BOOL result = FALSE; if (CryptAcquireContextW(&hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) if (CryptCreateHash(hCryptProv, CALG_SHA1, 0, 0, &hHash)) if (CryptHashData(hHash, (BYTE*)key, strlen(key), 0)) if (CryptDeriveKey(hCryptProv, CALG_AES_256, hHash, 0, &hKey)) { DWORD dwMode = CRYPT_MODE_CBC; result = CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&dwMode, 0); } if (hHash != NULL) CryptDestroyHash(hHash); return result; } BOOL AesEncrypt(string &data) { BOOL result = TRUE; string iv(AES_BLOCK_SIZE, 0); // 生成随机IV if (!CryptGenRandom(hCryptProv, AES_BLOCK_SIZE, (BYTE*)&iv[0])) return FALSE; // 设置密钥的IV参数 if (!CryptSetKeyParam(hKey, KP_IV, (BYTE*)iv.c_str(), 0)) return FALSE; // 保存原始明文长度,准备加密 DWORD dwDataLen = data.length(); DWORD dwBufferLen = dwDataLen + AES_BLOCK_SIZE; // 预留填充空间 data.resize(dwBufferLen); // 加密明文 if (!CryptEncrypt(hKey, NULL, TRUE, 0, (BYTE*)&data[0], &dwDataLen, dwBufferLen)) return FALSE; data.resize(dwDataLen); // 将IV附加到密文末尾,一起传输 data += iv; return TRUE; } BOOL AesDecrypt(string &data) { // 检查数据长度至少包含IV和密文 if (data.length() <= AES_BLOCK_SIZE) return FALSE; // 从密文末尾提取IV string iv = data.substr(data.length() - AES_BLOCK_SIZE, AES_BLOCK_SIZE); // 移除IV,剩余部分是加密后的密文 data = data.substr(0, data.length() - AES_BLOCK_SIZE); // 设置密钥的IV参数为提取出的随机IV if (!CryptSetKeyParam(hKey, KP_IV, (BYTE*)iv.c_str(), 0)) return FALSE; // 解密密文 DWORD dwDataLen = data.length(); if (!CryptDecrypt(hKey, NULL, TRUE, 0, (BYTE*)&data[0], &dwDataLen)) return FALSE; data.resize(dwDataLen); return TRUE; } int main() { if (!AesInitialization()) { std::cout << "初始化失败" << std::endl; return 1; } string message = "my mother & your mother were hanging up clothes"; string encrypted = message; if (!AesEncrypt(encrypted)) { std::cout << "加密失败" << std::endl; return 1; } string decrypted = encrypted; if (!AesDecrypt(decrypted)) { std::cout << "解密失败" << std::endl; return 1; } std::cout << "原始明文: " << message << std::endl; std::cout << "解密结果: " << decrypted << std::endl; // 清理资源 if (hKey) CryptDestroyKey(hKey); if (hCryptProv) CryptReleaseContext(hCryptProv, 0); return 0; }
关键修正点
- 加密时:先加密明文,再将IV附加到密文末尾,而非把IV和明文拼接后加密
- 解密时:先从密文末尾提取IV,再用该IV解密剩余内容
- 增加了资源清理逻辑,避免内存泄漏
- 增加了长度检查,防止非法数据导致崩溃
内容的提问来源于stack exchange,提问作者JeffR
相关产品推荐
相关产品推荐

