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 strings
proceso (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.
Respuestas:
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
strings
en 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:
Si crea key1 con la misma longitud de bytes,
key
puede usar valores de bytes (completamente) aleatorios y luego calcularkey2
: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
ykey2
en 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.
fuente
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!
fuente
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.
fuente
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();
fuente
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; // ... }
fuente
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;
fuente
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.
fuente
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.
fuente
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 :)
fuente
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.
fuente
Un método que probé recientemente es:
part1
part2
part1
ypart2
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
part1
ypart2
, 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);
fuente
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");
fuente
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.
fuente
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.
fuente