c++ - library - ¿Cómo limpiar(sobrescribir con bytes aleatorios) std:: string buffer interno?
string c++ library (6)
El estándar dice explícitamente que no debe escribir en el const char*
devuelto por data()
, así que no lo haga.
Hay formas perfectamente seguras de obtener un puntero modificable en su lugar:
if (secretString.size())
OpenSSL_cleanse(&secretString.front(), secretString.size());
O si la cadena ya se ha reducido y desea asegurarse de que se elimine toda su capacidad:
if (secretString.capacity()) {
secretString.resize(secretString.capacity());
OpenSSL_cleanse(&secretString.front(), secretString.size());
}
Considere un escenario, donde se usa std::string
para almacenar un secreto . Una vez que se consume y ya no es necesario, sería bueno limpiarlo, es decir, sobrescribir la memoria que lo contenía, ocultando así el secreto .
std::string
proporciona una función const char* data()
devuelve un puntero a (desde C ++ 11) memoria continua.
Ahora, ya que la memoria es continua y la variable se destruirá justo después de la limpieza debido al final del alcance, ¿sería seguro:
char* modifiable = const_cast<char*>(secretString.data());
OpenSSL_cleanse(modifiable, secretString.size());
Según la norma citada aquí:
$ 5.2.11 / 7 - Nota: Dependiendo del tipo de objeto, una operación de escritura a través del puntero, valor l o puntero al miembro de datos resultante de un
const_cast
queconst_cast
unconst_cast
68 puede producir un comportamiento indefinido (7.1.5.1) .
Eso indicaría lo contrario, pero ¿las condiciones anteriores (continuas, para ser eliminadas) lo hacen seguro?
Hay una mejor respuesta: ¡no !
std::string
es una clase diseñada para ser fácil de usar y eficiente. No fue diseñado teniendo en cuenta la criptografía, por lo que hay pocas garantías escritas para ayudarlo. Por ejemplo, no hay garantías de que sus datos no se hayan copiado en ningún otro lugar. En el mejor de los casos, podría esperar que la implementación de un compilador en particular le ofrezca el comportamiento que desea.
Si realmente desea tratar un secreto como un secreto, debe manejarlo utilizando herramientas diseñadas para manejar secretos. De hecho, debe desarrollar un modelo de amenaza para las capacidades que tiene su atacante y elegir sus herramientas en consecuencia.
Probablemente sea seguro. Pero no está garantizado.
Sin embargo, desde C++11
, una std::string
debe implementarse como datos contiguos para que pueda acceder de forma segura a su matriz interna utilizando la dirección de su primer elemento &secretString[0]
.
if(!secretString.empty()) // avoid UB
{
char* modifiable = &secretString[0];
OpenSSL_cleanse(modifiable, secretString.size());
}
Puedes usar std::fill
para rellenar la cadena con basura:
std::fill(str.begin(),str.end(), 0);
Tenga en cuenta que simplemente borrar o reducir la cadena (con métodos como clear
o shrink_to_fit
) no garantiza que los datos de la cadena se eliminarán de la memoria del proceso. Los procesos malintencionados pueden volcar la memoria del proceso y pueden extraer el secreto si la cadena no se sobrescribe correctamente.
Bono: Curiosamente, la capacidad de desechar los datos de la cadena por razones de seguridad obliga a algunos lenguajes de programación como Java a devolver las contraseñas como char[]
y no como String
. En Java, String
es inmutable, por lo que "trash" hará una nueva copia de la cadena. Por lo tanto, necesita un objeto modificable como char[]
que no usa copiar en escritura.
Editar: si su compilador optimiza esta llamada, puede usar indicadores específicos del compilador para asegurarse de que la función de eliminación de datos no se optimice:
#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
Solución probada en CentOS 6, Debian 8 y Ubuntu 16.04 (g ++ / clang ++, O0, O1, O2, O3):
secretString.resize(secretString.capacity(), ''/0'');
OPENSSL_cleanse(&secretString[0], secretString.size());
secretString.clear();
Si estuvieras realmente paranoico, podrías aleatorizar los datos en la cadena limpia, para no revelar la longitud de la cadena o una ubicación que contenía datos confidenciales:
#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();
}
Podrías sembrar el rand () si quisieras también.
std :: string es una mala elección para almacenar secretos. Dado que las cuerdas son copiables y, a veces, las copias pasan desapercibidas, su secreto puede "tener piernas". Además, las técnicas de expansión de cuerdas pueden causar múltiples copias de fragmentos (o de todos) sus secretos.
La experiencia dicta una clase movible, no copiable, limpiada al destruir, no inteligente (sin copias difíciles debajo del capó).