OpenSSL密码内存泄漏

huangapple go评论71阅读模式
英文:

openssl crypto memory leak

问题

我在使用 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());

        // 初始化解密操作
        if (1 != EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), nullptr, keyArr, ivArr, 0)) {
            EVP_CIPHER_CTX_free(ctx);
            return -3;
        }

        // 提供要解密的消息,并获取明文输出
        if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, dataArr, dataLength)) {
            EVP_CIPHER_CTX_free(ctx);
            return -4;
        }

        plaintext_len = len;

        // 完成解密
        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);

        // 清理
        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);
}

我尝试了许多组合,但使用字符串和向量仍然是一样的 - 解密后的字符串仍然存在于内存中。

在退出函数作用域之前:

[![enter image description here][1]][1]

在退出函数作用域之后:

[![enter image description here][2]][2]

以及在该内存转储中:

[![enter image description here][3]][3]

另一个:

[![enter image description here][4]][4]


<details>
<summary>英文:</summary>
I have a problem with openssl crypto library, after doing decryption even after exiting function scope, decrypted string is not removed from memory. My code:

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&lt;const unsigned char *&gt;(key.data());
const auto *ivArr = reinterpret_cast&lt;const unsigned char *&gt;(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, &amp;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, &amp;len)) {
EVP_CIPHER_CTX_free(ctx);
return -5;
}
plaintext_len += len;
decrypted = crypto::vector&lt;unsigned char&gt;(plaintext, plaintext + plaintext_len);
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
free(plaintext);
return 0;
} catch (exception &amp;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&lt;unsigned char&gt; decrypted;
int i = crypto::aes_decrypt(crypto::base64_decode(b64), key, iv, decrypted);
if (i != 0) {
cout &lt;&lt; &quot;Error: &quot; &lt;&lt; i &lt;&lt; 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 &lt;&lt; &quot;Click to clear&quot; &lt;&lt; endl;
string input;
getline(cin, input);

}


I tried many combinations, but with string and vector still the same - decrypted string is still in memory
Before exiting function scope: 
[![enter image description here][1]][1]
After exiting function scope:
[![enter image description here][2]][2]
and inside that memory dump:
[![enter image description here][3]][3]
and another:
[![enter image description here][4]][4]
Any suggestions how can i deal with it? I think its something wrong with aes decrypt implementation, because in memory dump there are many encryption methods related to openssl crypto library
[1]: https://i.stack.imgur.com/9TF9v.png
[2]: https://i.stack.imgur.com/PXxdz.png
[3]: https://i.stack.imgur.com/fZvvo.png
[4]: https://i.stack.imgur.com/UliKe.png
</details>
# 答案1
**得分**: 2
为了清理内存,OpenSSL提供了`OPENSSL_cleanse()`函数:
> `OPENSSL_cleanse()`会用0填充大小为`len`的`ptr`。...
在释放内存之前,不能安全地使用诸如`memset()`之类的函数来清除内存 - 编译器允许优化这些调用,因为它们根据C和C++标准对程序没有影响。 C在可选的Annex K中提供了`memset_s()`函数,*将*清除内存:
> ...与memset不同,memset_s函数的任何调用都应严格按照([5.1.2.3](https://port70.net/~nsz/c/c11/n1570.html#5.1.2.3))中描述的抽象机器规则进行评估。也就是说,memset_s函数的任何调用都应假定s和n指示的内存可能在将来可以访问,因此必须包含c指示的值。
您还需要小心使用C++容器,如`vector`。您无法显式控制这些C++容器使用的内存,因此不能保证它们不会在内存中留下您敏感数据的副本。
<details>
<summary>英文:</summary>
To clean memory, OpenSSL provides [the `OPENSSL_cleanse()` function](https://www.openssl.org/docs/man3.1/man3/OPENSSL_cleanse.html):
&gt; `OPENSSL_cleanse()` fills `ptr` of size `len` with a string of 0&#39;s. ...
You can not safely use functions such as `memset()` to clear memory before you deallocate it - compilers are allowed to optimize away such calls since they have no effect on the program per the C and C++ standards.  C does provide [the `memset_s()` function in the optional Annex K](https://port70.net/~nsz/c/c11/n1570.html#K.3.7.4.1), and that *will* clear the memory:
&gt; ... Unlike memset, any call to the memset_s function shall be evaluated strictly according to the rules of the abstract machine as described in ([5.1.2.3](https://port70.net/~nsz/c/c11/n1570.html#5.1.2.3)). That is, any call to the memset_s function shall assume that the memory indicated by s and n may be accessible in the future and thus must contain the values indicated by c.
You also need to be careful using C++ containers like `vector`.  You do not have explicit control of the memory used by such C++ containers, so there&#39;s no guarantee that they won&#39;t leave a copy of your sensitive data somewhere in memory.
</details>
# 答案2
**得分**: 0
我找到了我在这里寻找的内容:
https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
typedef std::basic_string<char, std::char_traits<char>, zallocator<char> > secure_string;
这个secure_string在离开函数作用域后会从内存中擦除。
<details>
<summary>英文:</summary>
I found what i was looking for here:
https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
typedef std::basic_string&lt;char, std::char_traits&lt;char&gt;, zallocator&lt;char&gt; &gt; secure_string;
that secure_string is wiped out from memory after will leave function scope
</details>

huangapple
  • 本文由 发表于 2023年6月1日 06:35:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/76377690.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定