如何安全地清除 std::string?

发布于 2024-11-02 12:59:45 字数 356 浏览 1 评论 0原文

如何在 std::string 中存储敏感数据(例如:密码)?

我有一个应用程序,它提示用户输入密码并在连接设置期间将其传递到下游服务器。我想在建立连接后安全地清除密码值。

如果我将密码存储为 char * 数组,我可以使用 SecureZeroMemory 从进程内存中删除敏感数据。但是,我想在代码中避免使用 char 数组,并且正在寻找与 std::string 类似的内容?

How does one store sensitive data (ex: passwords) in std::string?

I have an application which prompts the user for a password and passes it to a downstream server during connection setup. I want to securely clear the password value after the connection has been established.

If I store the password as a char * array, I can use APIs like SecureZeroMemory to get rid of the sensitive data from the process memory. However, I want to avoid char arrays in my code and am looking for something similar for std::string?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

执妄 2024-11-09 12:59:45

根据此处给出的答案,我编写了一个分配器来安全地归零记忆。

#include <string>
#include <windows.h>

namespace secure
{
  template <class T> class allocator : public std::allocator<T>
  {
  public:

    template<class U> struct rebind { typedef allocator<U> other; };
    allocator() throw() {}
    allocator(const allocator &) throw() {}
    template <class U> allocator(const allocator<U>&) throw() {}

    void deallocate(pointer p, size_type num)
    {
      SecureZeroMemory((void *)p, num);
      std::allocator<T>::deallocate(p, num);
    }
  };

  typedef std::basic_string<char, std::char_traits<char>, allocator<char> > string;
}

int main()
{
  {
    secure::string bar("bar");
    secure::string longbar("baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar");
  }
}

然而,事实证明,根据 std::string 的实现方式,分配器甚至可能不会为小值调用。例如,在我的代码中,甚至不会为字符串 bar 调用 deallocate(在 Visual Studio 上)。

那么答案是我们不能使用 std::string 来存储敏感数据。当然,我们可以选择编写一个处理用例的新类,但我对使用定义的 std::string 特别感兴趣。

感谢大家的帮助!

Based on the answer given here, I wrote an allocator to securely zero memory.

#include <string>
#include <windows.h>

namespace secure
{
  template <class T> class allocator : public std::allocator<T>
  {
  public:

    template<class U> struct rebind { typedef allocator<U> other; };
    allocator() throw() {}
    allocator(const allocator &) throw() {}
    template <class U> allocator(const allocator<U>&) throw() {}

    void deallocate(pointer p, size_type num)
    {
      SecureZeroMemory((void *)p, num);
      std::allocator<T>::deallocate(p, num);
    }
  };

  typedef std::basic_string<char, std::char_traits<char>, allocator<char> > string;
}

int main()
{
  {
    secure::string bar("bar");
    secure::string longbar("baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar");
  }
}

However, it turns out, depending on how std::string is implemented, it is possible that the allocator isn't even invoked for small values. In my code, for example, the deallocate doesn't even get called for the string bar (on Visual Studio).

The answer, then, is that we cannot use std::string to store sensitive data. Of course, we have the option to write a new class that handles the use case, but I was specifically interested in using std::string as defined.

Thanks everyone for your help!

朱染 2024-11-09 12:59:45

openssl 经历了几次安全擦除字符串的迭代,直到决定采用这种方法:

#include <string.h>
#include <string>

// Pointer to memset is volatile so that compiler must de-reference
// the pointer and can't assume that it points to any function in
// particular (such as memset, which it then might further "optimize")
typedef void* (*memset_t)(void*, int, size_t);

static volatile memset_t memset_func = memset;

void cleanse(void* ptr, size_t len) {
  memset_func(ptr, 0, len);
}

int main() {
  std::string secret_str = "secret";
  secret_str.resize(secret_str.capacity(), 0);
  cleanse(&secret_str[0], secret_str.size());
  secret_str.clear();

  return 0;
}

openssl went through a couple of iterations of securely erasing a string until it settled on this approach:

#include <string.h>
#include <string>

// Pointer to memset is volatile so that compiler must de-reference
// the pointer and can't assume that it points to any function in
// particular (such as memset, which it then might further "optimize")
typedef void* (*memset_t)(void*, int, size_t);

static volatile memset_t memset_func = memset;

void cleanse(void* ptr, size_t len) {
  memset_func(ptr, 0, len);
}

int main() {
  std::string secret_str = "secret";
  secret_str.resize(secret_str.capacity(), 0);
  cleanse(&secret_str[0], secret_str.size());
  secret_str.clear();

  return 0;
}
番薯 2024-11-09 12:59:45

这是一个复杂的话题,因为优化编译器会对你不利。像循环字符串和覆盖每个字符这样的直接方法并不可靠,因为编译器可能会优化它。与 memset 相同,但是,C11 添加了 memset_s,它应该是安全的,但可能不适用于所有平台。

因此,我强烈建议使用可信的加密库来完成该任务,并让其作者负责可移植性。安全擦除是一项基本操作(获取 C 数组并安全地覆盖它),所有库都必须在某个时候实现它。请注意,std::string 中的基础数据是连续的(如 C++11 标准所规定的 ,但实际上即使在 C++98/03 中你也可以假设它)。因此,您可以通过将 std::string 作为数组来使用加密库的安全擦除功能。

在 OpenSSL 中,安全擦除由 OPENSSL_cleanse 函数提供。 Crypto++ 有 memset_z

std::string secret;
// ...

// OpenSSL (#include <openssl/crypto.h> and link -lcrypto)
OPENSSL_cleanse(&secret[0], secret_str.size());

// Crypto++ (#include <crypto++/misc.h> and link -lcrypto++)
CryptoPP::memset_z(&secret[0], 0, secret.size());

顺便说一句,如果您从头开始设计 API,请考虑在存储机密时完全避免使用 std::string。 std::string 的设计目标并不是防止泄露秘密(或在调整大小或复制过程中泄露秘密的一部分)。

It is a complicated topic, as an optimizing compiler will work against you. Straightforward approaches like looping over the string and overwriting each character are not reliable, as the compiler might optimize it away. Same with memset, however, C11 added memset_s, which should be secure but might not be available on all platforms.

For that reason, I would strongly recommend to use a trusted crypto library for that task and let their authors take care of portability. Secure wiping is a basic operation (taking a C-array and overwriting it securely), which all libraries will have to implement at some point. Note that the underlying data in a std::string is contiguous (as mandated by the C++11 standard, but in practice even in C++98/03 you could assume it). Therefore, you can use the secure wiping facilities of the crypto library by treading the std::string as an array.

In OpenSSL, secure wiping is provided by the OPENSSL_cleanse function. Crypto++ has memset_z:

std::string secret;
// ...

// OpenSSL (#include <openssl/crypto.h> and link -lcrypto)
OPENSSL_cleanse(&secret[0], secret_str.size());

// Crypto++ (#include <crypto++/misc.h> and link -lcrypto++)
CryptoPP::memset_z(&secret[0], 0, secret.size());

As a side-note, if you design the API from scratch, consider avoiding std::string altogether when it comes to storing secrets. It was not a design goal of std::string to prevent leaking the secret (or parts of it during resizing or copying).

无语# 2024-11-09 12:59:45

为了后代,我曾经决定忽略这个建议并使用 std::string ,并使用 c_str() (并抛弃常量性)和易失性编写一个 Zero() 方法。如果我很小心,没有导致内容的重新分配/移动,并且我在需要清理的地方手动调用了zero(),那么一切似乎都正常运行。唉,我发现了另一个严重的缺陷:std::string也可以是引用计数的对象...在c_str()处爆破内存(或引用对象指向的内存)会在不知不觉中爆破另一个对象。

For posterity, I once decided to ignore this advice and use std::string anyway, and wrote a zero() method using c_str() (and casting away the constness) and volatile. If I was careful and didn't cause a reallocate/move of the contents, and I manually called zero() where I needed it clean, all seemed to function properly. Alas, I discovered another serious flaw the hard way: std::string can also be a referenced-counted object... blasting the memory at c_str() (or the memory the referenced object is pointing to) will unknowingly blast the other object.

没企图 2024-11-09 12:59:45

对于 Windows

std::string s("ASecret");
const char* const ptr = s.data();
SecureZeroMemory((void*)ptr, s.size());

这将根据 STL 内部结构安全地清除堆栈或堆中的数据。

适用于所有尺寸的琴弦,无论大小。

注意!

请勿使用 ptr 更改字符串的数据,这可能会导致长度增加或减少。

For Windows:

std::string s("ASecret");
const char* const ptr = s.data();
SecureZeroMemory((void*)ptr, s.size());

This shall securely clear the data from the stack or the heap depending on the STL internals.

Works on all sizes of the string no matter small or large.

Caution !

DO NOT USE ptr for altering the data of the string which might result in increasing or decreasing the length.

巴黎盛开的樱花 2024-11-09 12:59:45

std::string 基于 char*。作为 char* 的所有动态魔法背后的某个地方。因此,当您说不想在代码中使用 char* 时,您仍然在使用 char*,它只是在后台,上面堆满了一大堆其他垃圾。

我对进程内存不太有经验,但您始终可以迭代每个字符(在加密并将密码存储在数据库中之后?),并将其设置为不同的值。

还有一个 std::basic_string,但我不确定这会对您有什么帮助。

std::string is based on a char*. Somewhere behind all the dynamic magic as a char*. So when you say you don't want to use char*'s on your code, you are still using a char*, it's just in the background with a whole bunch of other garbage piled on top of it.

I'm not too experienced with process memory, but you could always iterate through each character (after you've encrypted and stored the password in a DB?), and set it to a different value.

There's also a std::basic_string, but I'm not sure what help that would do for you.

孤独患者 2024-11-09 12:59:45
std::string mystring;
...
std::fill(mystring.begin(), mystring.end(), 0);

或者更好地编写自己的函数:

void clear(std::string &v)
{
  std::fill(v.begin(), v.end(), 0);
}
std::string mystring;
...
std::fill(mystring.begin(), mystring.end(), 0);

or even better write your own function:

void clear(std::string &v)
{
  std::fill(v.begin(), v.end(), 0);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文