c++ - resueltos - imprimir cadena de caracteres en c
técnicas para oscurecer cadenas sensibles en C++ (13)
Necesito almacenar información confidencial (una clave de cifrado simétrica que deseo mantener en privado) 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 proceso de strings
(o cualquier otro que extraiga cadenas de una aplicación binaria) revelará la cadena anterior.
¿Qué técnicas deberían usarse para ocultar datos tan sensibles?
Editar:
De acuerdo, casi todos ustedes han dicho "su ejecutable puede ser de ingeniería inversa" , ¡por supuesto! Esta es una de mis preocupaciones, así que voy a despotricar un poco aquí:
¿Por qué es que el 99% (OK, así que 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 móvil entre la usabilidad perfecta y la falta de seguridad en un extremo, y la seguridad perfecta, pero no la usabilidad en el otro.
El punto es que elige su posición en esa escala móvil dependiendo de lo que esté tratando de hacer y del entorno en el que se ejecutará su software. No estoy escribiendo una aplicación para una instalación militar, estoy escribiendo una aplicación para una PC hogareña . Necesito cifrar datos en una red que no es de confianza con una clave de cifrado conocida. En estos casos, ¡la "seguridad a través de la oscuridad" es probablemente 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 ¿adivina 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 realmente esté vendiendo esto, pero entiendes mi punto). Esta tendencia del cielo azul "dejalo hacerlo de la mejor manera posible" en la programación entre los nuevos programadores es tonto, por decir lo menos.
Gracias por tomarse el tiempo para responder a esta pregunta; fueron de lo más útiles. Lamentablemente, solo puedo aceptar una respuesta, pero he votado todas las respuestas útiles.
Básicamente, cualquier persona que tenga acceso a su programa y un depurador puede y encontrará la clave en la aplicación si así lo desean.
Pero, si solo quiere asegurarse de que la clave no aparezca cuando se ejecutan strings
en su binario, podría, por ejemplo, asegurarse de que la clave no se encuentre dentro del rango imprimible.
Clave de ocultación 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 que la key
, puede usar (completamente) valores de bytes 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 key2
y key2
en su aplicación.
Protegiendo tu binario
Otro enfoque es usar una herramienta para proteger su binario. Por ejemplo, hay varias herramientas de seguridad que pueden asegurar que su binario esté ofuscado e inicie una máquina virtual en la que se ejecute. Esto hace que sea más difícil (er) depurar, y también es la forma convential en que se protegen muchas aplicaciones seguras de calidad comercial (también, lamentablemente, malware).
Una de las principales herramientas es Themida , que hace un trabajo increíble al proteger tus binarios. A menudo es utilizado por programas bien conocidos, como Spotify, para proteger 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, tal vez un poco desactualizada, de herramientas para proteger su binario .
Algunos de ellos son gratis.
Coincidencia de contraseña
Alguien aquí discutió hashing contraseña + sal.
Si necesita almacenar la clave para que coincida 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 una sal. El problema con esto, sin embargo, es que su aplicación tiene que saber la sal para poder hacer un solo sentido y comparar los valores hash resultantes. Por lo tanto, aún necesita almacenar la sal en algún lugar de su aplicación. Pero, como señala @Edward en los comentarios a continuación, esto efectivamente protegerá contra un ataque de diccionario usando, por ejemplo, tablas de arcoiris.
Finalmente, puedes usar una combinación de todas las técnicas anteriores.
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 copyright, por ejemplo, o un aviso de usuario falso o cualquier otra cosa que no será modificada por alguien que corrige código no relacionado), encriptado que se usa a sí mismo como una clave, pellizcó eso (agregando un poco de sal) y usó el resultado como una clave para encriptar lo que realmente quería encriptar.
Por supuesto, esto podría ser pirateado, pero se necesita un hacker determinado para hacerlo.
Creé una herramienta simple de encriptación para cadenas, puede generar automáticamente cadenas encriptadas y tiene algunas opciones adicionales para hacer eso, 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 ciclo 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;
Depende del contexto, pero podrías simplemente almacenar el hash de la clave más una sal (cadena constante, fácil de oscurecer).
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 hacker solo tiene que insertar una instrucción jmp en algún lugar para eludir todo el lote, pero eso es bastante más complicado que una simple búsqueda de texto.
Dependiente de lo que intenta proteger, como lo señala joshperry. Por experiencia, yo diría que si es parte de un esquema de licencia para proteger su software, entonces 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 pregunto si proteger esos datos con una clave privada almacenada localmente es una decisión acertada. Nuevamente se trata de lo que estás tratando de proteger.
EDITAR: Si vas a hacerlo, una combinación de técnicas que Chris señala será mucho mejor que rot13.
En lugar de almacenar la clave privada en el archivo ejecutable, puede solicitarla al usuario y almacenarla mediante un administrador de contraseñas externo, algo similar al Acceso de Mac OS X Keychain.
En primer lugar, tenga en cuenta que no hay nada que pueda hacer que detendrá a un hacker suficientemente determinado, y hay muchos a su alrededor. La protección en cada juego y consola se agrieta con el tiempo, por lo que esta 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 el de xoring (el operador ^) la cadena con otra cadena será lo suficientemente buena para hacer que la cadena sea imposible de buscar.
2) Separe la cuerda en pedazos - divida la cuerda y extraiga pequeños fragmentos en métodos extraños en módulos extraños. No facilite la búsqueda y encuentre el método con la cadena. Por supuesto, algún método tendrá que llamar a todos estos bits, pero todavía lo hace un poco más difícil.
3) Nunca construyas la cadena en la memoria: la mayoría de los hackers usan herramientas que les permiten ver la cadena en la memoria después de que la hayas codificado. Si es posible, evita esto. Si, por ejemplo, está enviando la llave a un servidor, envíela carácter por carácter, de modo que la cadena completa nunca esté cerca. Por supuesto, si lo está usando de algo así 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 simplemente agregue 1 a todo lo que produce, o haga una encriptación dos veces, o agregue un azúcar. Esto simplemente 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, hashing vado md5 o encriptación RSA.
Sobre todo, asegúrese de que no sea demasiado importante cuando (¡y será cuando, si su aplicación se vuelve lo suficientemente popular), se descubre su clave!
Estoy de acuerdo con @Checkers, tu ejecutable puede ser de ingeniería inversa.
Una forma un poco mejor es crearlo dinámicamente, por ejemplo:
std::string myKey = part1() + part2() + ... + partN();
Por supuesto, el almacenamiento de datos privados en el software que se envía al usuario siempre es un riesgo. Cualquier ingeniero suficientemente educado (y dedicado) podría aplicar ingeniería inversa a los datos.
Dicho esto, a menudo puede hacer las cosas lo suficientemente seguras elevando la barrera que las personas deben superar para revelar sus datos privados. Por lo general, es un buen compromiso.
En su caso, podría saturar sus cadenas con datos no imprimibles, y luego decodificar eso en tiempo de ejecución usando una función auxiliar 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;
// ...
}
Prueba this El código fuente explica cómo encriptar y desencriptar sobre la marcha todas las cadenas en un proyecto dado de Visual Studio c ++.
Si está en el usuario de Windows DPAPI, 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 malas desde una perspectiva de seguridad que no debería hacerlas. Cualquier persona que obtenga su clave privada es importante, no la guarde dentro de su programa. Dependiendo de cómo importar su aplicación, puede mantener sus claves privadas en una tarjeta inteligente, en una computadora remota con la que pueda hablar su código o puede hacer lo que hace la mayoría de las personas y mantenerlo en un lugar seguro en la computadora local (la "clave" tienda "que es como un registro seguro raro) que está protegido por permisos y toda la fuerza de su sistema operativo.
Este es un problema resuelto y la respuesta es NO mantener la clave dentro de tu programa :)
Un método que intenté recientemente es:
- Tome el hash (SHA256) de los datos privados y
part1
código comopart1
- Toma XOR de datos privados y su hash y rellena el código como
part2
- Relleno de datos: no lo guarde como char str [], pero rellene en tiempo de ejecución utilizando instrucciones de asignación (como se muestra en la macro a continuación)
- Ahora, genere los datos privados en tiempo de ejecución tomando el XOR de
part1
ypart2
- Paso adicional : calcula el hash de los datos generados y compáralo con
part1
. Verificará la integridad de los datos privados.
MACRO para poblar datos:
Supongamos que los datos privados son de 4 bytes. Definimos una macro 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 usa esta macro en el código donde necesitas guardar part1
y 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);
Una estrategia que he usado en el pasado es crear una matriz de personajes aparentemente aleatorios. Inicialmente inserta, y luego ubica sus caracteres particulares con un proceso algebraico donde cada paso de 0 a N arrojará un número <tamaño de la matriz que contiene el siguiente carácter en su cadena ofuscada. (¡Esta respuesta se siente ofuscada ahora!)
Ejemplo:
Dada una serie de caracteres (los números y guiones son solo de referencia)
0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL
Y una ecuación cuyos primeros seis resultados son: 3, 6, 7, 10, 21, 47
Daría la palabra "¡HOLA!" de la matriz de arriba.