how does one securely clear std::string?
Asked Answered
O

7

26

How does one store sensitive data (ex: passwords) in std::string?

I have an application which prompts the user for a password and passes it to a downstream server during connection setup. I want to securely clear the password value after the connection has been established.

If I store the password as a char * array, I can use APIs like SecureZeroMemory to get rid of the sensitive data from the process memory. However, I want to avoid char arrays in my code and am looking for something similar for std::string?

Ordway answered 18/4, 2011 at 2:37 Comment(3)
According to this link, std::strings are not designed for security purposes.Steffin
thanks Marlon, that means I have no choice but to clutter my method interfaces with char *buf, size_t len :)Ordway
@user34965: It's not that binary. You should design a class SecureString. It's a good idea to copy the interface of std::string so it's a drop-in replacement.Chrysolite
O
16

Based on the answer given here, I wrote an allocator to securely zero memory.

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

However, it turns out, depending on how std::string is implemented, it is possible that the allocator isn't even invoked for small values. In my code, for example, the deallocate doesn't even get called for the string bar (on Visual Studio).

The answer, then, is that we cannot use std::string to store sensitive data. Of course, we have the option to write a new class that handles the use case, but I was specifically interested in using std::string as defined.

Thanks everyone for your help!

Ordway answered 20/4, 2011 at 19:43 Comment(0)
M
9

openssl went through a couple of iterations of securely erasing a string until it settled on this approach:

#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;
}
Mcdonnell answered 4/12, 2019 at 15:56 Comment(0)
P
6

It is a complicated topic, as an optimizing compiler will work against you. Straightforward approaches like looping over the string and overwriting each character are not reliable, as the compiler might optimize it away. Same with memset, however, C11 added memset_s, which should be secure but might not be available on all platforms.

For that reason, I would strongly recommend to use a trusted crypto library for that task and let their authors take care of portability. Secure wiping is a basic operation (taking a C-array and overwriting it securely), which all libraries will have to implement at some point. Note that the underlying data in a std::string is contiguous (as mandated by the C++11 standard, but in practice even in C++98/03 you could assume it). Therefore, you can use the secure wiping facilities of the crypto library by treading the std::string as an array.

In OpenSSL, secure wiping is provided by the OPENSSL_cleanse function. Crypto++ has 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());

As a side-note, if you design the API from scratch, consider avoiding std::string altogether when it comes to storing secrets. It was not a design goal of std::string to prevent leaking the secret (or parts of it during resizing or copying).

Pfeiffer answered 8/5, 2020 at 13:12 Comment(3)
I think it's better to use secret.data() instead of &data[0] as the former guarantees that the string's buffer is ready read in contiguous mode. However, they say 1) Modifying the character array accessed through the const overload of data has undefined behavior., but I'm pretty sure that OPENSSL_cleanse(&secret[0], secret_str.size()); is already UB, so, well.Skardol
After reading a lot, I eventually lost my confidence and asked #5698502, we'll see another judgement XDSkardol
It's important to emphasize: It's not enough to wipe a std::string using something like OPENSSL_cleanse, memset_z, ormemset_s if your std::string went through a copy or resize.Afterward
B
4

For posterity, I once decided to ignore this advice and use std::string anyway, and wrote a zero() method using c_str() (and casting away the constness) and volatile. If I was careful and didn't cause a reallocate/move of the contents, and I manually called zero() where I needed it clean, all seemed to function properly. Alas, I discovered another serious flaw the hard way: std::string can also be a referenced-counted object... blasting the memory at c_str() (or the memory the referenced object is pointing to) will unknowingly blast the other object.

Bobbibobbie answered 3/10, 2012 at 12:22 Comment(2)
The ref count implementation is illegal as of C++11.Lebkuchen
@BaummitAugen it's illegal now, but worth noting if someone uses an old compiler. Anyways, how about legality of overwriting data through string's .data() or .c_str() pointers? Has that been changed to be non-UB in recent versions of the standard?Skardol
D
0

For Windows:

std::string s("ASecret");
const char* const ptr = s.data();
SecureZeroMemory((void*)ptr, s.size());

This shall securely clear the data from the stack or the heap depending on the STL internals.

Works on all sizes of the string no matter small or large.

Caution !

DO NOT USE ptr for altering the data of the string which might result in increasing or decreasing the length.

Discovert answered 26/4, 2022 at 10:10 Comment(0)
C
-1

std::string is based on a char*. Somewhere behind all the dynamic magic as a char*. So when you say you don't want to use char*'s on your code, you are still using a char*, it's just in the background with a whole bunch of other garbage piled on top of it.

I'm not too experienced with process memory, but you could always iterate through each character (after you've encrypted and stored the password in a DB?), and set it to a different value.

There's also a std::basic_string, but I'm not sure what help that would do for you.

Catamaran answered 18/4, 2011 at 3:3 Comment(5)
a manual overwrite of every character won't do - as the compiler can optimize such code if the string is about to be destroyed. see the question Marlon linked to in his comment above.Ordway
then overwrite each character, and then use the string ;)Catamaran
The main problem with overwriting anything in std::string is, that nothing in the world guarantees you that it will actually rewrite the memory the string was in or that it rewrites all the memory the string ever was in, because std::string may move the underlying buffer around.Floorman
There are some assembly instructions that could be included to tell the system and compiler that anything could read that data from another thread. That would prevent optimization but would also be platform specific.Fasciate
The points about "it's just a char* under the hood" are not useful. std::string is the same as std::basic_string. And the remainder of the answer is dangerously misinformed about the security implications of "just overwrite the characters" when it isn't done via some specialized mechanism. Use a purpose-built class instead of a std::string or use char* with memset_s (or something like it).Afterward
T
-2
std::string mystring;
...
std::fill(mystring.begin(), mystring.end(), 0);

or even better write your own function:

void clear(std::string &v)
{
  std::fill(v.begin(), v.end(), 0);
}
Tarnation answered 18/4, 2011 at 8:43 Comment(2)
Won't work. There is no guarantee that you overwrote all memory the string was ever in, because it may have been moved during some operation.Floorman
A sufficiently smart optimizer could also detect that you never use the zeros again, and skip the filling.Monarch

© 2022 - 2024 — McMap. All rights reserved.