You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于OpenSSL API实现指定参数AES-256-CBC解密的问题求助

问题描述

我想用OpenSSL API替代系统命令实现解密操作。此前通过命令openssl enc -aes-256-cbc -pbkdf2 -iter 310000 -md sha256 -salt -in file.run -out encrypted_data.enc -pass pass:Password!加密数据,原本用system调用对应解密命令能正常解密,但改用OpenSSL API编写的代码运行后,解密出的文件不正确,且生成的salt、密钥和IV与预期不符。以下是原系统命令解密代码和当前有问题的API代码,请求编写适配原加密参数的正确API解密代码。

原系统命令解密代码

bool CryptoProcessor::_decryptData(Any &response)
{
  String tempPrefix = String("/tmp/aaaa-") + uuid();

  std::ifstream input(_inputFile, std::ios::binary); // 输入文件是加密文件,头部包含签名和对称密钥
  input.seekg(SIGNATURE_SIZE + KEY_SIZE, std::ios::beg);

  String encryptedDataFileName = tempPrefix + ".encrypted-data";
  OutputFileStream encryptedDataFile;
  encryptedDataFile.open(encryptedDataFileName, std::ios::binary);

  char chunk[CHUNK_SIZE];
  while (!input.eof())
  {
    input.read(chunk, CHUNK_SIZE);
    int bytesRead = input.gcount();
    encryptedDataFile.write(chunk, bytesRead);
  }
  encryptedDataFile.close();
  input.close();

  String runFileName = tempPrefix + ".run";
  String command = String("openssl enc -aes-256-cbc -d -pbkdf2 -iter 310000 -p -md sha256 -salt -pass pass:") + _symmetricKey + " -in '" + encryptedDataFileName + "' -out '" + runFileName +"'";
// 加-p参数是为了查看salt、密钥和IV的值,对比API实现是否正确
  logger(LOG_INFO) << command << endl;
  ANY_ASSERT(system(command.c_str()) == 0, "Decryption failed")
  return true;
}

当前有问题的API解密代码

// Create an OpenSSL BIO object for input and output
    BIO *bio_in = BIO_new_file(encryptedDataFileName.c_str(), "rb");
    BIO *bio_out = BIO_new_file(runFileName.c_str(), "wb");

    if (!bio_in || !bio_out)
    {
        logger(LOG_ERROR) << "Failed to open input/output BIOs" << std::endl;
        return false;
    }

    // Create an EVP decryption context
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx)
    {
        BIO_free_all(bio_in);
        BIO_free_all(bio_out);
        logger(LOG_ERROR) << "Failed to create cipher context" << std::endl;
        return false;
    }

    // Set the AES-256-CBC cipher
    if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, nullptr, nullptr) != 1)
    {
        EVP_CIPHER_CTX_free(ctx);
        BIO_free_all(bio_in);
        BIO_free_all(bio_out);
        logger(LOG_ERROR) << "Failed to initialize decryption context" << std::endl;
        return false;
    }

    EVP_CIPHER_CTX_set_padding(ctx, 0); // Disable padding

    // Set the symmetric key using PBKDF2
    unsigned char derivedKey[32]; // 256 bits for AES-256
    int iterCount = 310000;
    logger(LOG_INFO) << "Iterations: " << iterCount << std::endl;
    const char *passphrase = _symmetricKey.c_str();
    logger(LOG_INFO) << "Passphrase: " << _symmetricKey << std::endl;
    unsigned char salt[PKCS5_SALT_LEN];
    RAND_bytes(salt, sizeof(salt));

    unsigned char iv[16];
    RAND_bytes(iv, sizeof(iv));

    if (PKCS5_PBKDF2_HMAC(passphrase, -1, salt, sizeof(salt), iterCount, EVP_sha256(), 32, derivedKey) != 1)
    {
        EVP_CIPHER_CTX_free(ctx);
        BIO_free_all(bio_in);
        BIO_free_all(bio_out);
        logger(LOG_ERROR) << "Failed to derive encryption key using PBKDF2" << std::endl;
        return false;
    }
    logger(LOG_INFO) << "Derived Key: ";
    for (int i = 0; i < 32; ++i)
    {
        logger(LOG_INFO) << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(derivedKey[i]);
    }
    logger(LOG_INFO) << std::dec << std::endl;

    // Set the key and IV for decryption
    if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, derivedKey, nullptr) != 1)
    {
        EVP_CIPHER_CTX_free(ctx);
        BIO_free_all(bio_in);
        BIO_free_all(bio_out);
        logger(LOG_ERROR) << "Failed to set decryption key and IV" << std::endl;
        return false;
    }
修正后的API解密代码

原代码的核心问题包括:随机生成salt/IV而非从加密文件读取、错误禁用填充、未完成解密流程。以下是适配原加密参数的正确实现:

bool CryptoProcessor::_decryptData(Any &response)
{
    String tempPrefix = String("/tmp/aaaa-") + uuid();

    // 提取加密数据部分(和原代码逻辑一致)
    std::ifstream input(_inputFile, std::ios::binary);
    input.seekg(SIGNATURE_SIZE + KEY_SIZE, std::ios::beg);

    String encryptedDataFileName = tempPrefix + ".encrypted-data";
    OutputFileStream encryptedDataFile;
    encryptedDataFile.open(encryptedDataFileName, std::ios::binary);

    char chunk[CHUNK_SIZE];
    while (!input.eof())
    {
        input.read(chunk, CHUNK_SIZE);
        int bytesRead = input.gcount();
        encryptedDataFile.write(chunk, bytesRead);
    }
    encryptedDataFile.close();
    input.close();

    String runFileName = tempPrefix + ".run";
    FILE *inFile = fopen(encryptedDataFileName.c_str(), "rb");
    FILE *outFile = fopen(runFileName.c_str(), "wb");

    if (!inFile || !outFile)
    {
        logger(LOG_ERROR) << "Failed to open input/output files" << std::endl;
        if (inFile) fclose(inFile);
        if (outFile) fclose(outFile);
        return false;
    }

    // 读取加密文件头部的Salt标识和salt值
    unsigned char saltHeader[8];
    unsigned char salt[PKCS5_SALT_LEN]; // PKCS5_SALT_LEN是8字节
    if (fread(saltHeader, 1, 8, inFile) != 8 || memcmp(saltHeader, "Salted__", 8) != 0)
    {
        logger(LOG_ERROR) << "Invalid encrypted file header (missing Salted__)" << std::endl;
        fclose(inFile);
        fclose(outFile);
        return false;
    }
    if (fread(salt, 1, sizeof(salt), inFile) != sizeof(salt))
    {
        logger(LOG_ERROR) << "Failed to read salt from encrypted file" << std::endl;
        fclose(inFile);
        fclose(outFile);
        return false;
    }

    // 创建解密上下文
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx)
    {
        logger(LOG_ERROR) << "Failed to create cipher context" << std::endl;
        fclose(inFile);
        fclose(outFile);
        return false;
    }

    // 派生密钥和IV:OpenSSL enc命令用PBKDF2生成32字节密钥+16字节IV,共48字节
    unsigned char keyIv[48];
    int iterCount = 310000;
    const char *passphrase = _symmetricKey.c_str();
    if (PKCS5_PBKDF2_HMAC(passphrase, -1, salt, sizeof(salt), iterCount, EVP_sha256(), sizeof(keyIv), keyIv) != 1)
    {
        logger(LOG_ERROR) << "Failed to derive key and IV using PBKDF2" << std::endl;
        EVP_CIPHER_CTX_free(ctx);
        fclose(inFile);
        fclose(outFile);
        return false;
    }

    unsigned char *key = keyIv;
    unsigned char *iv = keyIv + 32; // 前32字节是密钥,后16字节是IV

    // 初始化AES-256-CBC解密上下文,保留默认PKCS#7填充
    if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key, iv) != 1)
    {
        logger(LOG_ERROR) << "Failed to initialize decryption context" << std::endl;
        EVP_CIPHER_CTX_free(ctx);
        fclose(inFile);
        fclose(outFile);
        return false;
    }

    // 执行解密流程
    unsigned char inBuf[1024];
    unsigned char outBuf[1024 + EVP_MAX_BLOCK_LENGTH]; // 预留填充空间
    int inLen, outLen;
    bool success = true;

    while ((inLen = fread(inBuf, 1, sizeof(inBuf), inFile)) > 0)
    {
        if (EVP_DecryptUpdate(ctx, outBuf, &outLen, inBuf, inLen) != 1)
        {
            success = false;
            break;
        }
        fwrite(outBuf, 1, outLen, outFile);
    }

    if (success)
    {
        // 处理最后一块的填充
        if (EVP_DecryptFinal_ex(ctx, outBuf, &outLen) != 1)
        {
            success = false;
        }
        else
        {
            fwrite(outBuf, 1, outLen, outFile);
        }
    }

    // 清理资源
    EVP_CIPHER_CTX_free(ctx);
    fclose(inFile);
    fclose(outFile);

    if (!success)
    {
        logger(LOG_ERROR) << "Decryption failed" << std::endl;
        return false;
    }

    logger(LOG_INFO) << "Decryption completed successfully" << std::endl;
    return true;
}

关键修正点说明

  • 读取文件头部的salt:openssl enc -salt加密的文件开头是Salted__(8字节)+ 8字节salt,解密时必须读取这个salt,不能随机生成。
  • 派生密钥和IV:原命令用PBKDF2生成48字节数据,前32字节是AES-256密钥,后16字节是CBC模式的IV,而非单独生成IV。
  • 保留填充:原加密使用默认的PKCS#7填充,解密时不能禁用填充,否则会导致最后一块数据错误。
  • 完整解密流程:实现了从文件读取数据、执行解密更新、处理最终填充块的完整逻辑。

内容的提问来源于stack exchange,提问作者Manoj Chavva

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 13:34:55