How to cleanse (overwrite with random bytes) std::string internal buffer?

梦想与她 提交于 2019-12-03 09:34:11

It is probably safe. But not guaranteed.

However, since C++11, a std::string must be implemented as contiguous data so you can safely access its internal array using the address of its first element &secretString[0].

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

The standard explicitly says you must not write to the const char* returned by data(), so don't do that.

There are perfectly safe ways to get a modifiable pointer instead:

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

Or if the string might have been shrunk already and you want to ensure its entire capacity is wiped:

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

std::string is a poor choice to store secrets. Since strings are copyable and sometimes copies go unnoticed, your secret may "get legs". Furthermore, string expansion techniques may cause multiple copies of fragments (or all of) your secrets.

Experience dictates a movable, non-copyable, wiped clean on destroy, unintelligent (no tricky copies under-the-hood) class.

You can use std::fill to fill the string with trash:

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

Do note that simply clearing or shrinking the string (with methods such clear or shrink_to_fit) does not guarantee that the string data will be deleted from the process memory. Malicious processes may dump the process memory and can extract the secret if the string is not overwritten correctly.

Bonus: Interestingly, the ability to trash the string data for security reasons forces some programming languages like Java to return passwords as char[] and not String. In Java, String is immutable, so "trashing" it will make a new copy of the string. Hence, you need a modifiable object like char[] which does not use copy-on-write.

Edit: if your compiler does optimize this call out, you can use specific compiler flags to make sure a trashing function will not be optimized out:

#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

There's a better answer: don't!

std::string is a class which is designed to be userfriendly and efficient. It was not designed with cryptography in mind, so there are few guarantees written into it to help you out. For example, there's no guarantees that your data hasn't been copied elsewhere. At best, you could hope that a particular compiler's implementation offers you the behavior you want.

If you actually want to treat a secret as a secret, you should handle it using tools which are designed for handling secrets. In fact, you should develop a threat model for what capabilities your attacker has, and choose your tools accordingly.

Tested solution on CentOS 6, Debian 8 and Ubuntu 16.04 (g++/clang++, O0, O1, O2, O3):

secretString.resize(secretString.capacity(), '\0');
OPENSSL_cleanse(&secretString[0], secretString.size());
secretString.clear();

If you were really paranoid you could randomise the data in the cleansed string, so as not to give away the length of the string or a location that contained sensitive data:

#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();
}

You could seed the rand() if you wanted also.

You could also do similar string cleansing without openssl dependency, by using explicit_bzero to null the contents:

#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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!