如何清理(用随机字节覆盖)std :: string内部缓冲区?

时间:2016-08-01 15:41:55

标签: c++ c++11 stdstring

考虑一个场景,其中std::string用于存储秘密。一旦它被消耗并且不再需要它,最好清理它,即覆盖包含它的内存,从而隐藏秘密

std::string提供了一个函数const char* data(),返回指向(自C ++ 11以来)连续内存的指针。

现在,由于内存是连续的,因为范围结束,变量将在清理后立即销毁,是否安全:

char* modifiable = const_cast<char*>(secretString.data());
OpenSSL_cleanse(modifiable, secretString.size());

根据此处引用的标准:

  

$ 5.2.11 / 7 - 注意:根据对象的类型,通过指针,左值或指向数据成员的指针进行写入操作,const_cast会导致const-qualifier < sup> 68 可能会产生不确定的行为(7.1.5.1)。

否则会提出建议,但是上述条件(连续,即将被删除)是否安全?

6 个答案:

答案 0 :(得分:17)

这可能是安全的。但不能保证。

但是,由于C++11std::string必须实现为连续数据,因此您可以使用其第一个元素&secretString[0]的地址安全地访问其内部数组。

if(!secretString.empty()) // avoid UB
{
    char* modifiable = &secretString[0];
    OpenSSL_cleanse(modifiable, secretString.size());
}

答案 1 :(得分:14)

std :: string是存储机密的不良选择。由于字符串是可复制的,有时复制品不会引起注意,因此您的秘密可能会得到支持&#34;此外,字符串扩展技术可能会导致您的秘密片段(或全部)的多个副本。

经验决定了一个可移动的,不可复制的,擦拭干净的破坏,非智能(没有棘手的副本)类。

答案 2 :(得分:14)

该标准明确规定您不得写信至const char*返回的data(),因此请勿这样做。

有一种非常安全的方法可以获得可修改的指针:

if (secretString.size())
  OpenSSL_cleanse(&secretString.front(), secretString.size());

或者如果字符串可能已经缩小并且您想要确保其整个容量被擦除:

if (secretString.capacity()) {
  secretString.resize(secretString.capacity());
  OpenSSL_cleanse(&secretString.front(), secretString.size());
}

答案 3 :(得分:12)

您可以使用std::fill用垃圾填充字符串:

std::fill(str.begin(),str.end(), 0);

请注意,简单地清除或缩小字符串(使用clearshrink_to_fit等方法)并不能保证字符串数据将从进程内存中删除。恶意进程可能会转储进程内存,如果字符串未被正确覆盖,则可以提取秘密。

奖励:有趣的是,出于安全原因而删除字符串数据的能力迫使像Java这样的编程语言将密码返回为char[]而不是String。在Java中,String是不可变的,因此&#34;垃圾&#34;它将创建一个新的字符串副本。因此,您需要一个可修改的对象,如char[],它不使用copy-on-write。

编辑:如果你的编译器 优化了这个调用,你可以使用特定的编译器标志来确保不会优化垃圾功能:

#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

答案 4 :(得分:5)

有一个更好的答案:

std::string是一个旨在提高用户友好性和效率的课程。它的设计并没有考虑密码学,因此很少有保证可以帮助您解决问题。例如,我们无法保证您的数据未被复制到其他位置。充其量,您可能希望特定编译器的实现为您提供所需的行为。

如果您确实希望将秘密视为秘密,则应使用专为处理机密而设计的工具来处理它。实际上,您应该针对攻击者具备的功能开发威胁模型,并相应地选择您的工具。

答案 5 :(得分:0)

在CentOS 6,Debian 8和Ubuntu 16.04上测试的解决方案(g ++ / clang ++,O0,O1,O2,O3):

secretString.resize(secretString.capacity(), '\0');
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(), '\0');
  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(), '\0');
  explicit_bzero(&secretString[0], secretString.size());
  secretString.clear();

  return 0;
}