You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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');
?>

关键修复说明

  1. GCM标签处理:C++加密后获取16字节标签并和IV、密文一起保存;PHP解密时拆分出标签,作为openssl_decrypt的第6个参数传入。
  2. 二进制文件操作:C++用二进制模式写入文件,PHP直接读取二进制数据,避免字符编码导致的数据损坏。
  3. EVP_BytesToKey逻辑对齐:修正PHP版本的哈希迭代逻辑,确保生成和C++完全一致的密钥与IV。
  4. 资源安全:C++代码添加了资源释放逻辑,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Smit Modi

火山引擎 最新活动