如何在 std::string
中存储敏感数据(例如:密码)?
我有一个应用程序,在连接设置期间提示用户输入密码并将其传递给下游服务器。我想在建立连接后安全地清除密码值。
如果我将密码存储为 char *
数组,则可以使用诸如 SecureZeroMemory 等API从进程内存中清除敏感数据。然而,我想避免在我的代码中使用 char 数组,并寻找类似于 std::string
的东西?
如何在 std::string
中存储敏感数据(例如:密码)?
我有一个应用程序,在连接设置期间提示用户输入密码并将其传递给下游服务器。我想在建立连接后安全地清除密码值。
如果我将密码存储为 char *
数组,则可以使用诸如 SecureZeroMemory 等API从进程内存中清除敏感数据。然而,我想避免在我的代码中使用 char 数组,并寻找类似于 std::string
的东西?
根据这里给出的答案,我编写了一个分配器以安全地清零内存。
#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
(在Visual Studio中),甚至没有调用deallocate
。
因此,答案是我们不能使用std::string
来存储敏感数据。当然,我们可以编写一个新类来处理这种情况,但我特别想使用定义好的std::string
。
感谢大家的帮助!
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
也是如此,但是C11添加了memset_s,它应该是安全的,但可能无法在所有平台上使用。std::string
中的底层数据是连续的(由C++11标准规定,但实际上即使在C++98/03中也可以假定它)。因此,您可以将std::string
视为数组,并使用加密库的安全擦除功能。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());
std::string
。std::string
的设计目标并不是防止泄露机密信息(或在调整大小或复制期间泄露其部分)。secret.data()
而不是&data[0]
,因为前者保证了字符串缓冲区已准备好以连续模式读取。然而,他们说1)通过data的const重载访问修改字符数组具有未定义行为。
但我很确定OPENSSL_cleanse(&secret[0], secret_str.size());
已经是UB了,所以,嗯。 - quetzalcoatlOPENSSL_cleanse
、memset_z
或memset_s
之类的东西来擦除std::string是不够的。 - josaphatv为了纪念,我曾经决定忽略这个建议并且仍旧使用std::string,编写了一个零()方法,使用c_str()(并强制转换成volatile)。如果我小心一些,不会导致重分配/移动内容,而且在需要清除时手动调用zero(),所有情况似乎都可以正常运行。可惜,我发现另一个严重缺陷:std::string也可以是一个引用计数的对象……在c_str()(或引用对象指向的内存)处炸毁内存将不知不觉地炸毁其他对象。
std::string s("ASecret");
const char* const ptr = s.data();
SecureZeroMemory((void*)ptr, s.size());
这将根据STL内部机制,安全地清除堆栈或堆中的数据。
适用于所有大小的字符串,无论是小还是大。
请勿使用ptr
来更改字符串的数据,否则可能会导致长度增加或减少。
std::string基于char*。在所有动态魔法的背后,实际上是一个char*。因此,当你说你不想在你的代码中使用char*时,你仍然在使用char*,只是在背景下有一堆其他垃圾堆积在其上。
我对进程内存不是很有经验,但你可以迭代每个字符(在加密并将密码存储在数据库之后),并将其设置为不同的值。
还有一个std::basic_string,但我不确定它对你有什么帮助。
std::string
中覆盖任何内容的主要问题是,世界上没有任何保证它实际上会重写字符串所在的内存,或者它是否重写了字符串曾经存在的所有内存,因为std::string
可能会移动底层缓冲区。 - Jan Hudecstd::string
与std::basic_string
相同。而且,当通过某些专门的机制进行覆盖字符时,“仅仅覆盖字符”的安全影响的其余部分是危险的错误信息。使用专门构建的类替代std::string
或者使用带有memset_s
(或类似的)的char*
。 - josaphatvstd::string mystring;
...
std::fill(mystring.begin(), mystring.end(), 0);
或者更好的是编写你自己的函数:
void clear(std::string &v)
{
std::fill(v.begin(), v.end(), 0);
}
char *buf, size_t len
:) - ajd.class SecureString
。最好复制std :: string
的接口,这样它可以直接替换。 - MSalters