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
相关产品推荐
相关产品推荐

