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

OpenSSL Crypto解密后明文未从内存清除问题求助

问题

使用OpenSSL Crypto库执行AES解密后,即使退出函数作用域,解密得到的明文仍残留在内存中。尝试过string和vector等容器组合,问题依旧,内存转储显示明文始终存在,且包含大量OpenSSL相关方法。相关代码如下:

int crypto::aes_decrypt(const crypto::vector<unsigned char> &data, const crypto::string &key, const crypto::string &iv,
                        crypto::vector<unsigned char> &decrypted) {
    try {
        int len;
        int plaintext_len;
        const auto *dataArr = data.data();
        size_t dataLength = data.size();

        if (key.length() != 32 || iv.length() != 16) {
            return -1;
        }

        auto *plaintext = new unsigned char[dataLength + 16];

        EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
        if (ctx == nullptr) {
            return -2;
        }

        const auto *keyArr = reinterpret_cast<const unsigned char *>(key.data());
        const auto *ivArr = reinterpret_cast<const unsigned char *>(iv.data());

        /*
         * Initialise the decryption operation. IMPORTANT - ensure you use a key
         * and IV size appropriate for your cipher
         * In this example we are using 256-bit AES (i.e. a 256-bit key). The
         * IV size for *most* modes is the same as the block size. For AES this
         * is 128 bits
         */
        if (1 != EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), nullptr, keyArr, ivArr, 0)) {
            EVP_CIPHER_CTX_free(ctx);
            return -3;
        }

        /*
        * Provide the message to be decrypted, and obtain the plaintext output.
        * EVP_DecryptUpdate can be called multiple times if necessary.
        */
        if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, dataArr, dataLength)) {
            EVP_CIPHER_CTX_free(ctx);
            return -4;
        }

        plaintext_len = len;

        /*
         * Finalise the decryption. Further plaintext bytes may be written at
         * this stage.
         */

        if (1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
            EVP_CIPHER_CTX_free(ctx);
            return -5;
        }

        plaintext_len += len;
        decrypted = crypto::vector<unsigned char>(plaintext, plaintext + plaintext_len);

        /* Clean up */
        EVP_CIPHER_CTX_free(ctx);
        free(plaintext);
        return 0;

    } catch (exception &e) {
#ifdef DEBUG_SHARED_LIBRARIES
        cout << "Error while aes decrypting: " << e.what() << endl;
#endif
        return -99;
    }
}
void test() {
    string key = "abcdefghijklmnopabcdefghijklmnop";
    string iv = "1234567890123456";
    string b64 = "82fgiOvXUXrnkGeRsCjKgA==";
    
    vector<unsigned char> decrypted;
    int i = crypto::aes_decrypt(crypto::base64_decode(b64), key, iv, decrypted);
    if (i != 0) {
        cout << "Error: " << i << endl;
        return;
    }

    string dec = {decrypted.begin(), decrypted.end()};

//    cout << "Decrypted raw: " << string_utils::char_array_to_hex(decrypted.data(), decrypted.size()) << endl;
    cout << "Decrypted: " << dec << endl;

    cout << "Click to clear" << endl;
    string input;
    getline(cin, input);
}

解决建议

  • 主动清理堆内存缓冲区:代码中用new分配的plaintext缓冲区,调用free只会标记内存可复用,不会清除数据。必须在free前用OpenSSL提供的OPENSSL_cleanse覆盖内容,避免编译器优化掉内存赋值操作:

    OPENSSL_cleanse(plaintext, dataLength + 16);
    free(plaintext);
    
  • 清理容器内的敏感数据:vector和string销毁时不会自动擦除内存内容,需主动覆盖:

    • 对decrypted向量:
      OPENSSL_cleanse(decrypted.data(), decrypted.size());
      decrypted.clear();
      
    • 对test函数中的dec字符串:
      OPENSSL_cleanse(&dec[0], dec.size());
      dec.clear();
      
  • 重置OpenSSL上下文:EVP_CIPHER_CTX_free前调用EVP_CIPHER_CTX_reset,清除上下文内部缓存的敏感数据:

    EVP_CIPHER_CTX_reset(ctx);
    EVP_CIPHER_CTX_free(ctx);
    
  • 减少内存拷贝次数:当前代码将plaintext的数据拷贝到decrypted向量,导致明文多份残留。可以直接让decrypted提前分配空间,解密操作直接写入向量内存:

    // 替换new分配的plaintext,直接使用decrypted的内存
    decrypted.resize(dataLength + 16);
    unsigned char* plaintext = decrypted.data();
    
    // 后续解密逻辑直接写入plaintext
    // ...
    
    // 最后调整向量到实际明文长度
    decrypted.resize(plaintext_len);
    
  • 规避编译器优化:编译时避免可能保留敏感数据的优化,比如给处理敏感数据的函数添加volatile限定,或使用编译器特定属性(如GCC的__attribute__((noinline))),防止优化导致数据残留。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 23:55:34