Técnicas para ocultar cadenas sensibles en C ++


87

Necesito almacenar información confidencial (una clave de cifrado simétrica que quiero mantener privada) en mi aplicación C ++. El enfoque simple es hacer esto:

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

Sin embargo, ejecutar la aplicación a través del stringsproceso (o cualquier otro que extraiga cadenas de una aplicación binaria) revelará la cadena anterior.

¿Qué técnicas deberían utilizarse para ocultar estos datos sensibles?

Editar:

De acuerdo, casi todos ustedes han dicho "su ejecutable puede ser modificado por ingeniería inversa" - ¡por supuesto! Este es un motivo de preocupación mío, así que voy a despotricar un poco aquí:

¿Por qué el 99% (de acuerdo, quizás exagero un poco) de todas las preguntas relacionadas con la seguridad en este sitio se responden con un torrente de "no hay forma posible de crear un programa perfectamente seguro"? Eso no es útil. ¡responder! La seguridad es una escala variable entre la usabilidad perfecta y la falta de seguridad en un extremo y la seguridad perfecta pero sin usabilidad en el otro.

El punto es que eliges tu posición en esa escala móvil dependiendo de lo que intentes hacer y del entorno en el que se ejecutará tu software. No estoy escribiendo una aplicación para una instalación militar, estoy escribiendo una aplicación para una PC doméstica . Necesito cifrar datos a través de una red que no sea de confianza con una clave de cifrado conocida. En estos casos, la "seguridad a través de la oscuridad" probablemente sea lo suficientemente buena. Claro, alguien con suficiente tiempo, energía y habilidad podría aplicar ingeniería inversa al binario y encontrar la contraseña, pero ¿adivinen qué? No me importa

El tiempo que me lleva implementar un sistema seguro de primer nivel es más caro que la pérdida de ventas debido a las versiones descifradas (no es que esté vendiendo esto, pero entiendes mi punto). Esta tendencia de "cielo azul" de "hagámoslo de la mejor manera posible" en la programación entre los nuevos programadores es una tontería por decir lo menos.

Gracias por tomarse el tiempo para responder esta pregunta; fueron de gran ayuda. Desafortunadamente, solo puedo aceptar una respuesta, pero he votado a favor de todas las respuestas útiles.


2
Quizás, si describe lo que está tratando de lograr con la clave de cifrado, podamos ofrecerle consejos sobre cómo evitar la necesidad de hacerlo.
Joshperry


4
@Kirill: es difícil llamar a esta pregunta exactamente igual a la que mencionas. De hecho, la idea es la misma. La pregunta no es.
xtofl

1
@xtofl, eres libre de no votar. A mí me parecen dos preguntas idénticas.
Kirill V. Lyadvinsky

2
(No relacionado con despotricar) La definición misma de una "clave privada" es la mitad de un par de claves pública / privada que no se da. En este caso también, la clave privada es la que guarda en su servidor; el de su aplicación cliente es público.
MSalters

Respuestas:


42

Básicamente, cualquier persona con acceso a su programa y un depurador puede y va a encontrar la clave en la aplicación si así lo desean.

Pero, si solo desea asegurarse de que la clave no aparezca cuando se ejecute stringsen su binario, puede, por ejemplo, asegurarse de que la clave no esté dentro del rango imprimible.

Obscuring key con XOR

Por ejemplo, puede usar XOR para dividir la clave en dos matrices de bytes:

key = key1 XOR key2

Si crea key1 con la misma longitud de bytes, keypuede usar valores de bytes (completamente) aleatorios y luego calcular key2:

key1[n] = crypto_grade_random_number(0..255)
key2[n] = key[n] XOR key1[n]

Puede hacer esto en su entorno de compilación y luego solo almacenar key1 y key2en su aplicación.

Protegiendo tu binario

Otro enfoque es utilizar una herramienta para proteger su binario. Por ejemplo, existen varias herramientas de seguridad que pueden asegurarse de que su binario esté ofuscado e inicie una máquina virtual en la que se ejecuta. Esto hace que sea (más) difícil de depurar, y también es la forma convencional en que muchas aplicaciones seguras de grado comercial (también, por desgracia, malware) están protegidas.

Una de las principales herramientas es Themida , que hace un trabajo increíble al proteger sus binarios. A menudo lo utilizan programas bien conocidos, como Spotify, para protegerse contra la ingeniería inversa. Tiene características para evitar la depuración en programas como OllyDbg e Ida Pro.

También hay una lista más grande, quizás algo desactualizada, de herramientas para proteger su binario .
Algunos de ellos son gratuitos.

Coincidencia de contraseña

Alguien aquí discutió el hash de contraseña + sal.

Si necesita almacenar la clave para compararla con algún tipo de contraseña enviada por el usuario, debe usar una función de hash unidireccional, preferiblemente combinando nombre de usuario, contraseña y un sal. El problema con esto, sin embargo, es que su aplicación tiene que conocer la sal para poder hacer el one-way y comparar los hashes resultantes. Por lo tanto, aún debe almacenar la sal en algún lugar de su aplicación. Pero, como @Edward señala en los comentarios a continuación, esto protegerá eficazmente contra un ataque de diccionario utilizando, por ejemplo, tablas de arco iris.

Finalmente, puede utilizar una combinación de todas las técnicas anteriores.


Si es una contraseña que el usuario debe ingresar, almacene el hash + salt de la contraseña. Vea la respuesta a continuación.

1
@hapalibashi: ¿Cómo almacena la sal de forma segura en su aplicación? No creo que el OP necesitara un sistema de coincidencia de contraseñas unidireccional, solo una forma generalizada de almacenar claves estáticas.
csl

2
Descubrí que, al mirar programas desensamblados, generalmente no hay muchos XOR, por lo que si espera usar XOR para ocultar algo, tenga en cuenta que llaman la atención sobre sí mismos.
kb.

2
@kb - ese es un punto interesante. Supongo que verías ands y ors bit a bit sucediendo mucho más que xor. a ^ b == (a & ~ b) || (~ a & b)
Jeremy Powell

4
Conocer el valor de la sal no suele darle una ventaja a un adversario: el objetivo de la sal es evitar un "ataque de diccionario", mediante el cual el atacante ha calculado previamente los valores hash para muchas entradas probables. El uso de una sal los obliga a precalcular su diccionario con uno nuevo basado en la sal. Si cada sal se usa solo una vez, entonces el ataque de diccionario se vuelve completamente inútil.
Edward Dixon

8

En primer lugar, tenga en cuenta que no hay nada que pueda hacer que pueda detener a un pirata informático suficientemente decidido, y hay muchos por ahí. La protección en todos los juegos y consolas se rompe eventualmente, por lo que esto es solo una solución temporal.

Hay 4 cosas que puede hacer que aumentarán sus posibilidades de permanecer oculto por un tiempo.

1) Oculte los elementos de la cadena de alguna manera: algo obvio como xoring (el operador ^) la cadena con otra cadena será lo suficientemente bueno como para que la cadena sea imposible de buscar.

2) Divida la cadena en pedazos: divida su cadena y haga estallar partes de ella en métodos con nombres extraños en módulos extraños. No facilite la búsqueda y la búsqueda del método con la cadena. Por supuesto, algún método tendrá que llamar a todos estos bits, pero aún lo hace un poco más difícil.

3) Nunca cree la cadena en la memoria: la mayoría de los piratas informáticos utilizan herramientas que les permiten ver la cadena en la memoria después de haberla codificado. Si es posible, evite esto. Si, por ejemplo, está enviando la clave a un servidor, envíelo carácter por carácter, para que la cadena completa nunca esté disponible. Por supuesto, si lo está utilizando desde algo como la codificación RSA, entonces esto es más complicado.

4) Haga un algoritmo ad-hoc: además de todo esto, agregue un toque único o dos. Tal vez solo agregue 1 a todo lo que produce, o haga cualquier cifrado dos veces, o agregue un azúcar. Esto solo hace que sea un poco más difícil para el pirata informático que ya sabe qué buscar cuando alguien está usando, por ejemplo, el hash vanilla md5 o el cifrado RSA.

Sobre todo, asegúrese de que no sea demasiado importante cuándo (y lo será si su aplicación se vuelve lo suficientemente popular) ¡se descubra su clave!


5

Una estrategia que he usado en el pasado es crear una serie de caracteres aparentemente aleatorios. Inicialmente inserta y luego ubica sus caracteres particulares con un proceso algebraico donde cada paso de 0 a N producirá un número <tamaño de la matriz que contiene el siguiente carácter en su cadena ofuscada. (¡Esta respuesta se siente confusa ahora!)

Ejemplo:

Dada una matriz de caracteres (los números y guiones son solo para referencia)

0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL

Y una ecuación cuyos primeros seis resultados son: 3, 6, 7, 10, 21, 47

Rendiría la palabra "¡HOLA!" de la matriz de arriba.


Buena idea, supongo que podría mejorarlo aún más utilizando caracteres que no se
impriman

4

Estoy de acuerdo con @Checkers, su ejecutable puede tener ingeniería inversa.

Una forma un poco mejor es crearlo dinámicamente, por ejemplo:

std::string myKey = part1() + part2() + ... + partN();

Es cierto que eso evita que se revele la cadena al buscar el binario. Sin embargo, su cadena aún reside en la memoria. Sin embargo, su solución probablemente sea suficientemente buena para lo que estoy haciendo.
Thomi

@Thomi, puedes, por supuesto, destruirlo tan pronto como hayas terminado con él. Pero aún así, no es la mejor manera de manejar cadenas sensibles .
Nick Dandoulakis

... ya que destruirlo no garantiza realmente que la memoria se reutilice de inmediato.
Thomi

4

Por supuesto, almacenar datos privados en el software que se envía al usuario siempre es un riesgo. Cualquier ingeniero con la suficiente formación (y dedicación) podría aplicar ingeniería inversa a los datos.

Dicho esto, a menudo puede hacer que las cosas sean lo suficientemente seguras levantando la barrera que las personas deben superar para revelar sus datos privados. Suele ser un buen compromiso.

En su caso, podría saturar sus cadenas con datos no imprimibles y luego decodificarlos en tiempo de ejecución usando una función de ayuda simple, como esta:

void unscramble( char *s )
{
    for ( char *str = s + 1; *str != 0; str += 2 ) {
        *s++ = *str;
    }
    *s = '\0';
}

void f()
{
    char privateStr[] = "\001H\002e\003l\004l\005o";
    unscramble( privateStr ); // privateStr is 'Hello' now.

    string s = privateStr;
    // ...
}

4

Creé una herramienta de encriptación simple para cadenas, puede generar cadenas encriptadas automáticamente y tiene algunas opciones adicionales para hacerlo, algunos ejemplos:

Cadena como variable global:

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xCF, 0x34, 0xF8, 0x5F, 0x5C, 0x3D, 0x22, 0x13, 0xB4, 0xF3, 0x63, 0x7E, 0x6B, 0x34, 0x01, 0xB7, 0xDB, 0x89, 0x9A, 0xB5, 0x1B, 0x22, 0xD4, 0x29, 0xE6, 0x7C, 0x43, 0x0B, 0x27, 0x00, 0x91, 0x5F, 0x14, 0x39, 0xED, 0x74, 0x7D, 0x4B, 0x22, 0x04, 0x48, 0x49, 0xF1, 0x88, 0xBE, 0x29, 0x1F, 0x27 };

myKey[30] -= 0x18;
myKey[39] -= 0x8E;
myKey[3] += 0x16;
myKey[1] += 0x45;
myKey[0] ^= 0xA2;
myKey[24] += 0x8C;
myKey[44] ^= 0xDB;
myKey[15] ^= 0xC5;
myKey[7] += 0x60;
myKey[27] ^= 0x63;
myKey[37] += 0x23;
myKey[2] ^= 0x8B;
myKey[25] ^= 0x18;
myKey[12] ^= 0x18;
myKey[14] ^= 0x62;
myKey[11] ^= 0x0C;
myKey[13] += 0x31;
myKey[6] -= 0xB0;
myKey[22] ^= 0xA3;
myKey[43] += 0xED;
myKey[29] -= 0x8C;
myKey[38] ^= 0x47;
myKey[19] -= 0x54;
myKey[33] -= 0xC2;
myKey[40] += 0x1D;
myKey[20] -= 0xA8;
myKey[34] ^= 0x84;
myKey[8] += 0xC1;
myKey[28] -= 0xC6;
myKey[18] -= 0x2A;
myKey[17] -= 0x15;
myKey[4] ^= 0x2C;
myKey[9] -= 0x83;
myKey[26] += 0x31;
myKey[10] ^= 0x06;
myKey[16] += 0x8A;
myKey[42] += 0x76;
myKey[5] ^= 0x58;
myKey[23] ^= 0x46;
myKey[32] += 0x61;
myKey[41] ^= 0x3B;
myKey[31] ^= 0x30;
myKey[46] ^= 0x6C;
myKey[35] -= 0x08;
myKey[36] ^= 0x11;
myKey[45] -= 0xB6;
myKey[21] += 0x51;
myKey[47] += 0xD9;

Como cadena Unicode con bucle de descifrado:

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
wchar_t myKey[48];

myKey[21] = 0x00A6;
myKey[10] = 0x00B0;
myKey[29] = 0x00A1;
myKey[22] = 0x00A2;
myKey[19] = 0x00B4;
myKey[33] = 0x00A2;
myKey[0] = 0x00B8;
myKey[32] = 0x00A0;
myKey[16] = 0x00B0;
myKey[40] = 0x00B0;
myKey[4] = 0x00A5;
myKey[26] = 0x00A1;
myKey[18] = 0x00A5;
myKey[17] = 0x00A1;
myKey[8] = 0x00A0;
myKey[36] = 0x00B9;
myKey[34] = 0x00BC;
myKey[44] = 0x00B0;
myKey[30] = 0x00AC;
myKey[23] = 0x00BA;
myKey[35] = 0x00B9;
myKey[25] = 0x00B1;
myKey[6] = 0x00A7;
myKey[27] = 0x00BD;
myKey[45] = 0x00A6;
myKey[3] = 0x00A0;
myKey[28] = 0x00B4;
myKey[14] = 0x00B6;
myKey[7] = 0x00A6;
myKey[11] = 0x00A7;
myKey[13] = 0x00B0;
myKey[39] = 0x00A3;
myKey[9] = 0x00A5;
myKey[2] = 0x00A6;
myKey[24] = 0x00A7;
myKey[46] = 0x00A6;
myKey[43] = 0x00A0;
myKey[37] = 0x00BB;
myKey[41] = 0x00A7;
myKey[15] = 0x00A7;
myKey[31] = 0x00BA;
myKey[1] = 0x00AC;
myKey[47] = 0x00D5;
myKey[20] = 0x00A6;
myKey[5] = 0x00B0;
myKey[38] = 0x00B0;
myKey[42] = 0x00B2;
myKey[12] = 0x00A6;

for (unsigned int fngdouk = 0; fngdouk < 48; fngdouk++) myKey[fngdouk] ^= 0x00D5;

Cadena como variable global:

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xAF, 0xBB, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xA7, 0xA5, 0xB4, 0xA7, 0xB6, 0xB2, 0xA3, 0xB5, 0xB5, 0xB9, 0xB1, 0xB4, 0xA6, 0xB6, 0xAA, 0xA3, 0xB6, 0xBB, 0xB1, 0xB7, 0xB9, 0xAB, 0xAE, 0xAE, 0xB0, 0xA7, 0xB8, 0xA7, 0xB4, 0xA9, 0xB7, 0xA7, 0xB5, 0xB5, 0x42 };

for (unsigned int dzxykdo = 0; dzxykdo < 48; dzxykdo++) myKey[dzxykdo] -= 0x42;

No, he usado el sitio web stringencrypt.com para hacer el trabajo. Tiene ejemplos para C / C ++ stringencrypt.com/c-cpp-encryption que podría considerar usar para automatizar el cifrado de cadenas simple.
Bartosz Wójcik

2

Algo dependiente de lo que intentas proteger, como señala Joshperry. Por experiencia, diría que si es parte de algún esquema de licencias para proteger su software, no se moleste. Eventualmente lo harán ingeniería inversa. Simplemente use un cifrado simple como ROT-13 para protegerlo de ataques simples (líneas que corren cadenas sobre él). Si se trata de proteger los datos confidenciales de los usuarios, me preguntaría si proteger esos datos con una clave privada almacenada localmente es un acierto. Nuevamente, todo se reduce a lo que está tratando de proteger.

EDITAR: Si va a hacerlo, entonces una combinación de técnicas que Chris señala será mucho mejor que rot13.


2

Como se dijo antes, no hay forma de proteger totalmente su cuerda. Pero hay formas de protegerlo con una seguridad razonable.

Cuando tuve que hacer esto, puse una cadena de aspecto inocente en el código (un aviso de derechos de autor, por ejemplo, o algún mensaje de usuario falso o cualquier otra cosa que no será cambiada por alguien que corrige un código no relacionado), cifrado eso usándose a sí mismo como una clave, hash eso (agregando un poco de sal), y usé el resultado como una clave para cifrar lo que realmente quería cifrar.

Por supuesto, esto podría ser pirateado, pero se necesita un pirata informático determinado para hacerlo.


Buena idea: otra forma de oscuridad es usar una cadena que aún sea razonablemente fuerte (larga, puntuación y todo ese jazz) pero que obviamente no se ve como una contraseña.
Thomi

1

Si tiene un usuario DPAPI de Windows, http://msdn.microsoft.com/en-us/library/ms995355.aspx

Como decía una publicación anterior, si estás en Mac, usa el llavero.

Básicamente, todas estas lindas ideas sobre cómo almacenar su clave privada dentro de su binario son lo suficientemente pobres desde una perspectiva de seguridad que no debería hacerlas. Cualquiera que obtenga su clave privada es un gran problema, no la guarde dentro de su programa. Dependiendo de qué tan importante sea su aplicación, puede mantener sus claves privadas en una tarjeta inteligente, en una computadora remota con la que habla su código o puede hacer lo que la mayoría de la gente hace y guardarlo en un lugar muy seguro en la computadora local (la "clave store "que es como un extraño registro seguro) que está protegido por permisos y toda la fuerza de su sistema operativo.

Este es un problema resuelto y la respuesta NO es mantener la clave dentro de su programa :)


1

Prueba esto . El código fuente explica cómo cifrar y descifrar sobre la marcha todas las cadenas en un proyecto de Visual Studio c ++ determinado.


1

Un método que probé recientemente es:

  1. Tome hash (SHA256) de los datos privados y complételo en código como part1
  2. Tome XOR de datos privados y su hash y complételo en código como part2
  3. Completar datos: no los almacene como char str [], sino que complete en tiempo de ejecución usando instrucciones de asignación (como se muestra en la macro a continuación)
  4. Ahora, genere los datos privados en tiempo de ejecución tomando el XOR de part1ypart2
  5. Paso adicional : Calcule el hash de los datos generados y compárelo con part1. Verificará la integridad de los datos privados.

MACRO para completar datos:

Supongamos que los datos privados son de 4 bytes. Definimos una macro para ello que guarda los datos con instrucciones de asignación en algún orden aleatorio.

#define POPULATE_DATA(str, i0, i1, i2, i3)\
{\
    char *p = str;\
    p[3] = i3;\
    p[2] = i2;\
    p[0] = i0;\
    p[1] = i1;\
}

Ahora use esta macro en el código donde necesita guardar part1y part2, de la siguiente manera:

char part1[4] = {0};
char part2[4] = {0};
POPULATE_DATA(part1, 1, 2, 3, 4); 
POPULATE_DATA(part2, 5, 6, 7, 8);

1

Hay una ofuscación de proyecto (muy ligera) de solo encabezado hecha por adamyaxley que funciona perfectamente. Se basa en funciones lambda y macros y cifra cadenas literalmente con un cifrado XOR en tiempo de compilación. Si es necesario, podemos cambiar la semilla para cada cadena.

El siguiente código no almacenará la cadena "hola mundo" en el binario compilado.

#include "obfuscate.h"

int main()
{
  std::cout << AY_OBFUSCATE("Hello World") << std::endl;
  return 0;
}

Probé con c ++ 17 y visual studio 2019, verifiqué a través de IDA y confirmo que la cadena está oculta. Una ventaja preciosa en comparación con ADVobfuscator es que se puede convertir en un std :: string (aunque todavía está oculto en el binario compilado):

std::string var = AY_OBFUSCATE("string");

0

En lugar de almacenar la clave privada en su ejecutable, es posible que desee solicitarla al usuario y almacenarla por medio de un administrador de contraseñas externo , algo similar a Mac OS X Keychain Access.


bueno, sí y no ... normalmente estoy de acuerdo contigo, pero en este caso estoy tratando de ocultar esto al usuario del software, por lo que almacenarlo en un sistema externo no es una gran idea (muchos sistemas de llaveros pueden exponer las contraseñas como texto sin formato a los usuarios con la autorización adecuada). El software de llavero es ideal para las contraseñas de usuario, pero no tanto para las claves de cifrado de aplicaciones.
Thomi

si se siente inseguro (aunque dadas sus aclaraciones, tal vez sea adecuado), puede combinar un llavero y algo codificado :)

0

Depende del contexto, pero puede almacenar el hash de la clave más una sal (cadena constante, fácil de ocultar).

Luego, cuando (si) el usuario ingresa la clave, agrega la sal , calcula el hash y compara.

La sal probablemente sea innecesaria en este caso, detiene un ataque de diccionario de fuerza bruta si se puede aislar el hash (también se sabe que una búsqueda en Google funciona).

Un pirata informático todavía solo tiene que insertar una instrucción jmp en algún lugar para omitir todo, pero eso es bastante más complicado que una simple búsqueda de texto.


Esta es una clave de cifrado, no un hash de contraseña. Necesito la clave real para codificar y decodificar datos. El usuario nunca ve la clave, nunca se almacena fuera del binario.
Thomi
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.