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

C++ 如何安全地清除std::string?,c++,string,passwords,secure-coding,C++,String,Passwords,Secure Coding,如何在std::string中存储敏感数据(例如:密码) 我有一个应用程序,它会提示用户输入密码,并在连接设置期间将密码传递给下游服务器。我想在建立连接后安全地清除密码值 如果我将密码存储为char*数组,我可以使用类似API的方法从进程内存中删除敏感数据。但是,我希望在代码中避免使用字符数组,并且正在为std::string?std::string基于字符*。在所有动态魔法的背后,比如一个字符*。因此,当你说你不想在代码中使用char*,你仍然在使用char*,它只是在后台,上面堆着一大堆其他

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

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


如果我将密码存储为
char*
数组,我可以使用类似API的方法从进程内存中删除敏感数据。但是,我希望在代码中避免使用字符数组,并且正在为
std::string

std::string基于字符*。在所有动态魔法的背后,比如一个字符*。因此,当你说你不想在代码中使用char*,你仍然在使用char*,它只是在后台,上面堆着一大堆其他垃圾

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

还有一个std::basic_字符串,但我不确定这对您有什么帮助

std::string mystring;
...
std::fill(mystring.begin(), mystring.end(), 0);
或者最好编写自己的函数:

void clear(std::string &v)
{
  std::fill(v.begin(), v.end(), 0);
}
根据给出的答案,我编写了一个分配器来安全地将内存归零

#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::分配器
{
公众:
模板结构重新绑定{typedef分配器其他;};
分配器()抛出(){}
分配器(常量分配器&)throw(){}
模板分配器(常量分配器&)throw(){}
无效解除分配(指针p,大小\类型num)
{
SecureZeroMemory((void*)p,num);
std::allocator::deallocate(p,num);
}
};
typedef std::基本_字符串;
}
int main()
{
{
安全:字符串条(“条”);
安全:字符串长杆(“baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;
}
}
然而,事实证明,根据std::string的实现方式,甚至可能不会为小值调用分配器。例如,在我的代码中,
deallocate
甚至不会为字符串
bar
调用(在Visual Studio上)

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


谢谢大家的帮助

对于后代,我曾经决定忽略这个建议,无论如何都要使用std::string,并使用c_str()和volatile编写了一个zero()方法。如果我很小心,并且没有导致内容的重新分配/移动,并且在需要清除的地方手动调用zero(),那么所有这些似乎都正常工作。唉,我艰难地发现了另一个严重的缺陷:std::string也可以是一个被引用的计数对象。。。爆破c_str()处的内存(或引用对象指向的内存)将在不知情的情况下爆破另一个对象。

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;
}
#包括
#包括
//指向memset的指针是易变的,因此编译器必须取消引用
//指针,不能假定它指向中的任何函数
//特别的(比如memset,它可能会进一步“优化”)
typedef void*(*memset_t)(void*,int,size_t);
静态易失性memset\u t memset\u func=memset;
空隙清理(空隙*ptr,尺寸透镜){
memset_func(ptr,0,len);
}
int main(){
std::string secret\u str=“secret”;
secret_str.resize(secret_str.capacity(),0);
清除(&secret_str[0],secret_str.size());
秘密街清除();
返回0;
}

作为一个整体,这是一个复杂的话题。像在字符串上循环和覆盖每个字符这样简单的方法是不可靠的,因为编译器可能会对其进行优化。但是,与
memset
相同,添加了C11,它应该是安全的,但可能无法在所有平台上使用

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

在OpenSSL中,安全擦除由
OpenSSL\u clean
函数提供。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());
std::字符串机密;
// ...
//OpenSSL(#包含和链接-lcrypto)
OPENSSL_clean(&secret[0],secret_str.size());
//加密++(#包含和链接-lcrypto++)
CryptoPP::memset_z(&secret[0],0,secret.size());

作为一个旁注,如果从头开始设计API,考虑在存储秘密时避免<代码> STD::String 。

std::string
的设计目标不是为了防止泄露机密(或在调整大小或复制时泄露机密的一部分)。

根据,std::string不是为了安全目的而设计的。感谢Marlon,这意味着我别无选择,只能用
char*buf,size\u t len
:)@user34965:不是二进制的。您应该设计一个
类SecureString
。复制
std::string
的接口是一个好主意,因此它只是一个临时替代品。手动覆盖每个字符是不行的,因为如果字符串即将销毁,编译器可以优化这些代码。请参见上面注释中链接到的Marlon。然后覆盖每个字符,然后使用字符串;)在
std::string
中重写任何内容的主要问题是,世界上没有任何东西可以保证它会重写字符串所在的内存或重写所有