一个人如何存储敏感数据(例如:密码)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
实现方式的不同,分配器甚至可能不会为小值调用。例如,在我的代码中,deallocate
甚至没有调用字符串bar
(在 Visual Studio 上)。
那么,答案是我们不能使用 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,它应该是安全的,但可能并非在所有平台上都可用。
出于这个原因,我强烈建议为该任务使用受信任的加密库,并让他们的作者负责可移植性。安全擦除是一项基本操作(获取 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());
附带说明一下,如果您从头开始设计 API,请考虑std::string
在存储机密时完全避免。这不是std::string
防止泄露秘密(或在调整大小或复制过程中泄露秘密的一部分)的设计目标。
对于后人,我曾经决定忽略这个建议并使用 std::string ,并使用 c_str() (并抛弃 constness)和 volatile 编写了一个 zero() 方法。如果我很小心并且没有导致内容的重新分配/移动,并且我在需要清理的地方手动调用了 zero(),那么一切似乎都可以正常运行。唉,我发现了另一个严重的缺陷:std::string 也可以是一个引用计数的对象......在 c_str() 处爆破内存(或被引用对象指向的内存)会在不知不觉中爆破另一个对象.
std::string 基于 char*。在所有动态魔法背后的某个地方,就像一个 char*。所以当你说你不想在你的代码中使用 char* 时,你仍然在使用 char*,它只是在后台,上面堆着一大堆其他垃圾。
我对进程内存不太熟悉,但您总是可以遍历每个字符(在您加密并将密码存储在数据库中之后?),并将其设置为不同的值。
还有一个 std::basic_string,但我不确定它会对你有什么帮助。
std::string mystring;
...
std::fill(mystring.begin(), mystring.end(), 0);
甚至更好地编写自己的函数:
void clear(std::string &v)
{
std::fill(v.begin(), v.end(), 0);
}