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

ajd*_*jd. 17 c++ string passwords secure-coding

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

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

如果我将密码存储为char *数组,我可以使用SecureZeroMemory等 API 来清除进程内存中的敏感数据.但是,我想在代码中避免使用char数组,并且正在寻找类似的东西std::string

ajd*_*jd. 12

根据这里给出的答案,我写了一个分配器来安全地记忆零.

#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");
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢大家的帮助!


Phi*_*ßen 6

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

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

在 OpenSSL 中,安全擦除是由该OPENSSL_cleanse函数提供的。加密++有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());
Run Code Online (Sandbox Code Playgroud)

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

  • 需要强调的是:如果您的 std::string 进行了复制或调整大小,那么使用“OPENSSL_cleanse”、“memset_z”或“memset_s”之类的东西来擦除 std::string 是不够的。 (2认同)

eri*_*tin 5

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;
}
Run Code Online (Sandbox Code Playgroud)