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

时间:2011-04-18 02:37:11

标签: c++ string passwords secure-coding

如何在std::string

中存储敏感数据(例如:密码)

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

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

6 个答案:

答案 0 :(得分:12)

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

#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的实现方式,甚至可能不会为小值调用分配器。例如,在我的代码中,deallocate甚至没有调用字符串bar(在Visual Studio上)。

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

感谢大家的帮助!

答案 1 :(得分:2)

作为一个optimizing compiler will work against you,这是一个复杂的话题。简单的方法(例如遍历字符串和覆盖每个字符)并不可靠,因为编译器可能会对其进行优化。与memset相同,但是C11添加了memset_s,它应该是安全的,但可能并非在所有平台上都可用。

由于这个原因,我强烈建议使用受信任的加密库来执行该任务,并让其作者注意可移植性。安全擦除是一项基本操作(获取C数组并对其进行安全覆盖),所有库都必须在某个时候实现。请注意,std::string中的基础数据是连续的(与mandated by the C++11 standard一样,但是实际上即使在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());

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

答案 2 :(得分:1)

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

我对进程内存不是很熟悉,但是你总是可以迭代每个字符(在你加密并将密码存储在数据库中之后?),并将其设置为不同的值。

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

答案 3 :(得分:1)

对于后代,我曾经决定忽略这个建议并且无论如何都使用std :: string,并使用c_str()(并抛弃const)和volatile编写了一个zero()方法。如果我小心并且没有导致重新分配/移动内容,并且我手动调用零()我需要它干净,所有似乎都正常运行。唉,我发现了另一个严重的缺陷:std :: string也可以是一个引用计数的对象...在c_str()(或引用的对象所指向的内存)中爆破内存会在不知不觉中爆炸另一个对象

答案 4 :(得分:0)

std::string mystring;
...
std::fill(mystring.begin(), mystring.end(), 0);

甚至更好地编写自己的函数:

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

答案 5 :(得分:0)

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;
}