如何清理(用随机字节覆盖)std::string 内部缓冲区?
How to cleanse (overwrite with random bytes) std::string internal buffer?
考虑一个场景,其中 std::string
用于存储 秘密 。一旦它被消耗并且不再需要,最好清理它,即覆盖包含它的内存,从而隐藏 secret.
std::string
提供函数 const char* data()
返回指向 (C++11 起) 连续内存的指针。
现在,由于内存是连续的和,由于作用域结束,变量将在清理后立即销毁,是否安全:
char* modifiable = const_cast<char*>(secretString.data());
OpenSSL_cleanse(modifiable, secretString.size());
根据此处引用的标准:
.2.11/7 - Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to data member resulting from a const_cast
that casts away a const-qualifier
68 may produce undefined behavior (7.1.5.1).
否则会有其他建议,但上述条件(连续的,即将删除的)是否安全?
你可以使用std::fill
来用垃圾填满字符串:
std::fill(str.begin(),str.end(), 0);
请注意,简单地清除或收缩字符串(使用 clear
或 shrink_to_fit
等方法)并不能保证字符串数据将从进程内存中删除。如果字符串未被正确覆盖,恶意进程可能会转储进程内存并提取秘密。
奖励:有趣的是,出于安全原因而丢弃字符串数据的能力迫使某些编程语言(例如 Java 将 return 密码设置为 char[]
而不是 String
。在 Java 中,String
是不可变的,因此 "trashing" 它将创建一个新的字符串副本。因此,您需要一个像 char[]
这样不使用写时复制的可修改对象。
编辑:如果你的编译器确实优化了这个调用,你可以使用特定的编译器标志来确保垃圾处理函数不会被优化:
#ifdef WIN32
#pragma optimize("",off)
void trashString(std::string& str){
std::fill(str.begin(),str.end(),0);
}
#pragma optimize("",on)
#endif
#ifdef __GCC__
void __attribute__((optimize("O0"))) trashString(std::string& str) {
std::fill(str.begin(),str.end(),0);
}
#endif
#ifdef __clang__
void __attribute__ ((optnone)) trashString(std::string& str) {
std::fill(str.begin(),str.end(),0);
}
#endif
std::string 是存储秘密的糟糕选择。由于字符串是可复制的,并且有时复制会被忽视,因此您的秘密可能 "get legs"。此外,字符串扩展技术可能会导致您的秘密片段(或全部)的多个副本。
经验决定了一个可移动的、不可复制的、销毁时擦除干净的、不智能的(没有复杂的副本)class。
应该是安全的。但不能保证。
但是,由于 C++11
,std::string
必须作为连续数据实现,因此您可以使用其第一个元素的地址安全地访问其内部数组 &secretString[0]
.
if(!secretString.empty()) // avoid UB
{
char* modifiable = &secretString[0];
OpenSSL_cleanse(modifiable, secretString.size());
}
标准明确规定您不能写入 data()
返回的 const char*
,所以不要那样做。
有非常安全的方法来获取可修改的指针:
if (secretString.size())
OpenSSL_cleanse(&secretString.front(), secretString.size());
或者,如果字符串可能已经缩小并且您想要确保擦除其全部容量:
if (secretString.capacity()) {
secretString.resize(secretString.capacity());
OpenSSL_cleanse(&secretString.front(), secretString.size());
}
有一个更好的答案:不要!
std::string
是一个 class,旨在方便用户使用并提高效率。它在设计时并未考虑密码学,因此几乎没有写入任何保证来帮助您解决问题。例如,无法保证您的数据未被复制到其他地方。充其量,您可能希望特定编译器的实现为您提供所需的行为。
如果您真的想将机密视为机密,则应使用专为处理机密而设计的工具来处理它。事实上,您应该针对攻击者的能力开发一个威胁模型,并相应地选择您的工具。
在 CentOS 6、Debian 8 和 Ubuntu 16.04(g++/clang++、O0、O1、O2、O3)上测试的解决方案:
secretString.resize(secretString.capacity(), '[=10=]');
OPENSSL_cleanse(&secretString[0], secretString.size());
secretString.clear();
如果你真的很偏执,你可以随机化清理后的字符串中的数据,以免泄露字符串的长度或包含敏感数据的位置:
#include <string>
#include <stdlib.h>
#include <string.h>
typedef void* (*memset_t)(void*, int, size_t);
static volatile memset_t memset_func = memset;
void cleanse(std::string& to_cleanse) {
to_cleanse.resize(to_cleanse.capacity(), '[=11=]');
for (int i = 0; i < to_cleanse.size(); ++i) {
memset_func(&to_cleanse[i], rand(), 1);
}
to_cleanse.clear();
}
如果您也愿意,可以为 rand() 播种。
您也可以在不依赖 openssl 的情况下进行类似的字符串清理,方法是使用 explicit_bzero 将内容清空:
#include <string>
#include <string.h>
int main() {
std::string secretString = "ajaja";
secretString.resize(secretString.capacity(), '[=12=]');
explicit_bzero(&secretString[0], secretString.size());
secretString.clear();
return 0;
}
考虑一个场景,其中 std::string
用于存储 秘密 。一旦它被消耗并且不再需要,最好清理它,即覆盖包含它的内存,从而隐藏 secret.
std::string
提供函数 const char* data()
返回指向 (C++11 起) 连续内存的指针。
现在,由于内存是连续的和,由于作用域结束,变量将在清理后立即销毁,是否安全:
char* modifiable = const_cast<char*>(secretString.data());
OpenSSL_cleanse(modifiable, secretString.size());
根据此处引用的标准:
.2.11/7 - Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to data member resulting from a
const_cast
that casts away aconst-qualifier
68 may produce undefined behavior (7.1.5.1).
否则会有其他建议,但上述条件(连续的,即将删除的)是否安全?
你可以使用std::fill
来用垃圾填满字符串:
std::fill(str.begin(),str.end(), 0);
请注意,简单地清除或收缩字符串(使用 clear
或 shrink_to_fit
等方法)并不能保证字符串数据将从进程内存中删除。如果字符串未被正确覆盖,恶意进程可能会转储进程内存并提取秘密。
奖励:有趣的是,出于安全原因而丢弃字符串数据的能力迫使某些编程语言(例如 Java 将 return 密码设置为 char[]
而不是 String
。在 Java 中,String
是不可变的,因此 "trashing" 它将创建一个新的字符串副本。因此,您需要一个像 char[]
这样不使用写时复制的可修改对象。
编辑:如果你的编译器确实优化了这个调用,你可以使用特定的编译器标志来确保垃圾处理函数不会被优化:
#ifdef WIN32
#pragma optimize("",off)
void trashString(std::string& str){
std::fill(str.begin(),str.end(),0);
}
#pragma optimize("",on)
#endif
#ifdef __GCC__
void __attribute__((optimize("O0"))) trashString(std::string& str) {
std::fill(str.begin(),str.end(),0);
}
#endif
#ifdef __clang__
void __attribute__ ((optnone)) trashString(std::string& str) {
std::fill(str.begin(),str.end(),0);
}
#endif
std::string 是存储秘密的糟糕选择。由于字符串是可复制的,并且有时复制会被忽视,因此您的秘密可能 "get legs"。此外,字符串扩展技术可能会导致您的秘密片段(或全部)的多个副本。
经验决定了一个可移动的、不可复制的、销毁时擦除干净的、不智能的(没有复杂的副本)class。
应该是安全的。但不能保证。
但是,由于 C++11
,std::string
必须作为连续数据实现,因此您可以使用其第一个元素的地址安全地访问其内部数组 &secretString[0]
.
if(!secretString.empty()) // avoid UB
{
char* modifiable = &secretString[0];
OpenSSL_cleanse(modifiable, secretString.size());
}
标准明确规定您不能写入 data()
返回的 const char*
,所以不要那样做。
有非常安全的方法来获取可修改的指针:
if (secretString.size())
OpenSSL_cleanse(&secretString.front(), secretString.size());
或者,如果字符串可能已经缩小并且您想要确保擦除其全部容量:
if (secretString.capacity()) {
secretString.resize(secretString.capacity());
OpenSSL_cleanse(&secretString.front(), secretString.size());
}
有一个更好的答案:不要!
std::string
是一个 class,旨在方便用户使用并提高效率。它在设计时并未考虑密码学,因此几乎没有写入任何保证来帮助您解决问题。例如,无法保证您的数据未被复制到其他地方。充其量,您可能希望特定编译器的实现为您提供所需的行为。
如果您真的想将机密视为机密,则应使用专为处理机密而设计的工具来处理它。事实上,您应该针对攻击者的能力开发一个威胁模型,并相应地选择您的工具。
在 CentOS 6、Debian 8 和 Ubuntu 16.04(g++/clang++、O0、O1、O2、O3)上测试的解决方案:
secretString.resize(secretString.capacity(), '[=10=]');
OPENSSL_cleanse(&secretString[0], secretString.size());
secretString.clear();
如果你真的很偏执,你可以随机化清理后的字符串中的数据,以免泄露字符串的长度或包含敏感数据的位置:
#include <string>
#include <stdlib.h>
#include <string.h>
typedef void* (*memset_t)(void*, int, size_t);
static volatile memset_t memset_func = memset;
void cleanse(std::string& to_cleanse) {
to_cleanse.resize(to_cleanse.capacity(), '[=11=]');
for (int i = 0; i < to_cleanse.size(); ++i) {
memset_func(&to_cleanse[i], rand(), 1);
}
to_cleanse.clear();
}
如果您也愿意,可以为 rand() 播种。
您也可以在不依赖 openssl 的情况下进行类似的字符串清理,方法是使用 explicit_bzero 将内容清空:
#include <string>
#include <string.h>
int main() {
std::string secretString = "ajaja";
secretString.resize(secretString.capacity(), '[=12=]');
explicit_bzero(&secretString[0], secretString.size());
secretString.clear();
return 0;
}