C++加密PHP解密失败排查:密钥IV一致但openssl_decrypt返回false
AES-128-GCM解密失败(PHP返回false)的问题排查与修复
我来帮你定位并解决这个问题,你的代码里有几个核心错误导致解密失败,下面逐一分析并给出修复方案:
1. GCM模式缺失认证标签(Auth Tag)
GCM是带完整性认证的加密模式,解密时必须提供加密过程中生成的16字节认证标签才能通过校验,否则openssl_decrypt会直接返回false。你的C++代码完全没有处理这个关键部分:
- 加密完成后需要调用
EVP_CIPHER_CTX_ctrl获取标签 - 加密结果需要将IV + 密文 + 标签一起保存,解密时要拆分出这三部分
2. C++加密数据的输出方式错误
你用cout直接输出二进制加密数据,会导致大量不可打印字符(比如空字节、控制字符)丢失或被转换,必须用二进制模式写入文件才能完整保存数据。
3. PHP中EVP_BytesToKey的实现偏差
你的PHP版EVP_BytesToKey和OpenSSL官方逻辑不一致,OpenSSL的迭代哈希是基于上一次的哈希结果+密码+salt循环计算,需要修正这部分逻辑才能生成和C++一致的密钥与IV。
修复后的C++加密代码
#include <iostream> #include <fstream> #include <string> #include <openssl/evp.h> int main(int argc, char** args) { unsigned char *salt = (unsigned char*)"12345678"; unsigned char *data = (unsigned char*)"123456789123450"; unsigned int count = 5; int dlen = strlen((char*)data); unsigned int ksize = 16; unsigned int vsize = 12; unsigned char *key = new unsigned char[ksize]; unsigned char *iv = new unsigned char[vsize]; // 生成密钥和IV int ret = EVP_BytesToKey(EVP_aes_128_gcm(), EVP_sha1(), salt, data, dlen, count, key, iv); if (ret != ksize) { std::cout << "EVP_BytesToKey failed" << std::endl; return 1; } const EVP_CIPHER* m_cipher = EVP_aes_128_gcm(); EVP_CIPHER_CTX* m_encode = EVP_CIPHER_CTX_new(); if (!m_encode) { std::cout << "ERROR :: In encode Initiallization" << std::endl; return 1; } // 初始化加密上下文(分两步设置算法和密钥IV) if (EVP_EncryptInit_ex(m_encode, m_cipher, NULL, NULL, NULL) != 1) { std::cout << "EncryptInit_ex failed" << std::endl; EVP_CIPHER_CTX_free(m_encode); return 1; } if (EVP_EncryptInit_ex(m_encode, NULL, NULL, key, iv) != 1) { std::cout << "EncryptInit_ex (set key/iv) failed" << std::endl; EVP_CIPHER_CTX_free(m_encode); return 1; } unsigned char* plain = (unsigned char*)"My Name IS DON !!!"; int plain_len = strlen((char*)plain); // 密文缓冲区预留标签空间 unsigned char* encData = new unsigned char[plain_len + 16]; int c_len = 0; int f_len = 0; // 执行加密 if (EVP_EncryptUpdate(m_encode, encData, &c_len, plain, plain_len) != 1) { std::cout << "EncryptUpdate failed" << std::endl; EVP_CIPHER_CTX_free(m_encode); delete[] encData; return 1; } // 完成加密 if (EVP_EncryptFinal_ex(m_encode, encData + c_len, &f_len) != 1) { std::cout << "EncryptFinal_ex failed" << std::endl; EVP_CIPHER_CTX_free(m_encode); delete[] encData; return 1; } int ciphertext_len = c_len + f_len; // 获取GCM认证标签(16字节) unsigned char tag[16] = {0}; if (EVP_CIPHER_CTX_ctrl(m_encode, EVP_CTRL_GCM_GET_TAG, 16, tag) != 1) { std::cout << "Get tag failed" << std::endl; EVP_CIPHER_CTX_free(m_encode); delete[] encData; return 1; } // 二进制模式写入:IV(12字节) + 密文 + 标签(16字节) std::ofstream outfile("./abc_enc.txt", std::ios::binary); if (!outfile.is_open()) { std::cout << "Failed to open file" << std::endl; EVP_CIPHER_CTX_free(m_encode); delete[] encData; return 1; } outfile.write((char*)iv, vsize); outfile.write((char*)encData, ciphertext_len); outfile.write((char*)tag, 16); outfile.close(); // 清理资源 EVP_CIPHER_CTX_free(m_encode); delete[] key; delete[] iv; delete[] encData; std::cout << "Encryption completed successfully" << std::endl; return 0; }
修复后的PHP解密代码
<?php function EVP_BytesToKey($salt, $password, $key_len, $iv_len, $iterations = 1, $digest = 'sha1') { $key = ''; $iv = ''; $data = ''; // 循环生成足够的哈希数据,拆分密钥和IV while (strlen($key) < $key_len || strlen($iv) < $iv_len) { $data .= $password . $salt; $hash = openssl_digest($data, $digest); // 多轮迭代哈希 for ($i = 1; $i < $iterations; $i++) { $hash = openssl_digest(hex2bin($hash), $digest); } $data = hex2bin($hash); // 优先填充密钥,再填充IV if (strlen($key) < $key_len) { $key .= substr($data, 0, $key_len - strlen($key)); } if (strlen($iv) < $iv_len) { $iv .= substr($data, strlen($key) - $key_len, $iv_len - strlen($iv)); } } return ['key' => $key, 'iv' => $iv]; } function decrypt($encrypted_data, $password) { $method = "aes-128-gcm"; $salt = "12345678"; $iterations = 5; $iv_len = 12; $tag_len = 16; $key_len = 16; // 拆分加密数据:IV + 密文 + 标签 $iv = substr($encrypted_data, 0, $iv_len); $tag = substr($encrypted_data, -$tag_len); $ciphertext = substr($encrypted_data, $iv_len, -$tag_len); // 生成匹配的密钥和IV $genKeyData = EVP_BytesToKey($salt, $password, $key_len, $iv_len, $iterations); $key = $genKeyData['key']; // GCM模式解密必须传入认证标签 $ret = openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv, $tag); var_dump($ret); return $ret; } $file = './abc_enc.txt'; $fileData = file_get_contents($file); $decrypted = decrypt($fileData, '123456789123450'); ?>
关键修复说明
- GCM标签处理:C++加密后获取16字节标签并和IV、密文一起保存;PHP解密时拆分出标签,作为
openssl_decrypt的第6个参数传入。 - 二进制文件操作:C++用二进制模式写入文件,PHP直接读取二进制数据,避免字符编码导致的数据损坏。
- EVP_BytesToKey逻辑对齐:修正PHP版本的哈希迭代逻辑,确保生成和C++完全一致的密钥与IV。
- 资源安全:C++代码添加了资源释放逻辑,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Smit Modi




