Tecniche per oscurare stringhe sensibili in C ++


88

Ho bisogno di memorizzare informazioni sensibili (una chiave di crittografia simmetrica che voglio mantenere privata) nella mia applicazione C ++. L'approccio semplice è fare questo:

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

Tuttavia, l'esecuzione dell'applicazione attraverso il stringsprocesso (o qualsiasi altro che estrae stringhe da un'app binaria) rivelerà la stringa precedente.

Quali tecniche dovrebbero essere utilizzate per nascondere tali dati sensibili?

Modificare:

OK, così avete detto praticamente tutti "il vostro eseguibile può essere sottoposto a reverse engineering" - ovviamente! Questo è un mio cruccio, quindi ho intenzione di inveire un po 'qui:

Perché il 99% (OK, forse esagero un po ') di tutte le domande relative alla sicurezza su questo sito riceve una risposta con un torrente del tipo "non c'è modo di creare un programma perfettamente sicuro" - questo non è utile risposta! La sicurezza è una scala mobile tra la perfetta usabilità e nessuna sicurezza a un'estremità e la perfetta sicurezza ma nessuna usabilità all'altra.

Il punto è che scegli la tua posizione su quella scala mobile a seconda di cosa stai cercando di fare e dell'ambiente in cui verrà eseguito il tuo software. Non sto scrivendo un'app per un'installazione militare, sto scrivendo un'app per un PC domestico . Ho bisogno di crittografare i dati su una rete non attendibile con una chiave di crittografia pre-nota. In questi casi, la "sicurezza attraverso l'oscurità" è probabilmente sufficiente! Certo, qualcuno con abbastanza tempo, energia e abilità potrebbe decodificare il binario e trovare la password, ma indovina cosa? Non mi interessa:

Il tempo che mi ci vuole per implementare un sistema sicuro di prim'ordine è più costoso della perdita di vendite a causa delle versioni crackate (non che io stia effettivamente vendendo questo, ma ottieni il mio punto). Questa tendenza del "ci lascia fare nel miglior modo possibile" nella programmazione tra i nuovi programmatori è sciocca per non dire altro.

Grazie per aver dedicato del tempo per rispondere a questa domanda: sono stati di grande aiuto. Sfortunatamente posso accettare solo una risposta, ma ho votato a favore di tutte le risposte utili.


2
Forse, se descrivi ciò che stai cercando di ottenere con la chiave di crittografia, possiamo offrire consigli su come evitare di doverlo fare.
joshperry


4
@ Kirill: è difficile chiamare questa domanda esattamente come quella che hai menzionato. In effetti, l'idea è la stessa. La domanda non è.
xtofl

1
@xtofl, sei libero di non votare. A me sembrano due domande identiche.
Kirill V. Lyadvinsky

2
(Non correlato al rant) La definizione stessa di "chiave privata" è la metà di una coppia di chiavi pubblica / privata che non si fornisce. Anche in questo caso la chiave privata è quella che tieni sul tuo server; quello nella tua app client è pubblico.
MSalters

Risposte:


42

In sostanza, chiunque abbia accesso al vostro programma e un debugger può e sarà trovare la chiave nell'applicazione, se vogliono.

Ma, se vuoi solo assicurarti che la chiave non venga visualizzata durante l'esecuzione stringssul tuo binario, potresti per esempio assicurarti che la chiave non sia all'interno dell'intervallo stampabile.

Oscuramento della chiave con XOR

Ad esempio, potresti usare XOR per dividere la chiave in due array di byte:

key = key1 XOR key2

Se crei key1 con la stessa lunghezza in byte, keypuoi utilizzare valori di byte (completamente) casuali e quindi calcolare key2:

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

Puoi farlo nel tuo ambiente di compilazione, quindi archiviare key1e solo key2nella tua applicazione.

Proteggi il tuo binario

Un altro approccio consiste nell'utilizzare uno strumento per proteggere il tuo binario. Ad esempio, ci sono diversi strumenti di sicurezza che possono assicurarsi che il tuo binario sia offuscato e avvii una macchina virtuale su cui gira. Questo rende difficile (ehm) il debug, ed è anche il modo convenzionale con cui molte applicazioni sicure di livello commerciale (anche, ahimè, malware) sono protette.

Uno degli strumenti principali è Themida , che fa un ottimo lavoro nel proteggere i tuoi file binari. Viene spesso utilizzato da programmi noti, come Spotify, per proteggersi dal reverse engineering. Ha funzionalità per impedire il debug in programmi come OllyDbg e Ida Pro.

C'è anche un elenco più ampio, forse un po 'obsoleto, di strumenti per proteggere il tuo binario .
Alcuni di loro sono gratuiti.

Corrispondenza della password

Qualcuno qui ha discusso di hashing password + salt.

Se è necessario memorizzare la chiave per abbinarla a un qualche tipo di password inviata dall'utente, è necessario utilizzare una funzione di hashing unidirezionale, preferibilmente combinando nome utente, password e salt. Il problema con questo, tuttavia, è che la tua applicazione deve conoscere il sale per essere in grado di eseguire il one-way e confrontare gli hash risultanti. Quindi, quindi, devi ancora conservare il sale da qualche parte nella tua applicazione. Ma, come sottolinea @Edward nei commenti seguenti, questo proteggerà efficacemente da un attacco del dizionario usando, ad esempio, le tabelle arcobaleno.

Infine, puoi utilizzare una combinazione di tutte le tecniche sopra.


Se è una password che l'utente deve inserire, memorizza l'hash + salt della password. Vedi risposta sotto.

1
@hapalibashi: come conservi il sale in modo sicuro nella tua applicazione? Non penso che l'OP avesse bisogno di un sistema di corrispondenza delle password unidirezionale, solo un modo generalizzato di memorizzare le chiavi statiche.
csl

2
Ho scoperto che quando guardi i programmi disassemblati in genere non ci sono molti XOR, quindi se speri di usare XOR per oscurare qualcosa, tieni presente che attirano l'attenzione su se stessi.
kb.

2
@kb - questo è un punto interessante. Immagino che vedresti and bit per bit e or accadere molto più di xor. a ^ b == (a & ~ b) || (~ a & b)
Jeremy Powell

4
Conoscere il valore salt di solito non offre un vantaggio a un avversario: lo scopo del salt è evitare un "attacco dizionario", in base al quale l'attaccante ha pre-calcolato gli hash per molti input probabili. L'uso di un sale li costringe a pre-calcolare il loro dizionario con uno nuovo basato sul sale. Se ogni sale viene utilizzato una sola volta, l'attacco al dizionario diventa completamente inutile.
Edward Dixon

8

Prima di tutto, renditi conto che non c'è niente che puoi fare per fermare un hacker sufficientemente determinato, e ce ne sono molti in giro. La protezione su ogni gioco e console in giro alla fine è rotta, quindi questa è solo una soluzione temporanea.

Ci sono 4 cose che puoi fare per aumentare le possibilità di rimanere nascosti per un po '.

1) Nascondere gli elementi della stringa in qualche modo - qualcosa di ovvio come xoring (l'operatore ^) la stringa con un'altra stringa sarà abbastanza buona da rendere impossibile la ricerca della stringa.

2) Dividi la stringa in pezzi: dividi la stringa e pop di parti di essa in metodi con nomi strani in moduli strani. Non semplificare la ricerca e trovare il metodo con la stringa al suo interno. Ovviamente qualche metodo dovrà chiamare tutti questi bit, ma lo rende ancora un po 'più difficile.

3) Non creare mai la stringa in memoria: la maggior parte degli hacker utilizza strumenti che consentono loro di vedere la stringa in memoria dopo averla codificata. Se possibile, evita questo. Se ad esempio stai inviando la chiave a un server, inviala carattere per carattere, in modo che l'intera stringa non sia mai in giro. Ovviamente, se lo stai usando da qualcosa come la codifica RSA, allora questo è più complicato.

4) Esegui un algoritmo ad hoc: oltre a tutto questo, aggiungi uno o due colpi di scena unici. Magari aggiungi 1 a tutto ciò che produci, esegui due volte la crittografia o aggiungi uno zucchero. Questo rende solo un po 'più difficile per l'hacker che sa già cosa cercare quando qualcuno sta usando, ad esempio, l'hashing vanilla md5 o la crittografia RSA.

Soprattutto, assicurati che non sia troppo importante quando (e lo sarà quando la tua applicazione diventerà abbastanza popolare) la tua chiave verrà scoperta!


5

Una strategia che ho usato in passato è quella di creare una serie di personaggi apparentemente casuali. Inizialmente inserisci e quindi localizzi i tuoi caratteri particolari con un processo algebrico in cui ogni passaggio da 0 a N produrrà un numero <dimensione dell'array che contiene il carattere successivo nella stringa offuscata. (Questa risposta si sente offuscata ora!)

Esempio:

Dato un array di caratteri (numeri e trattini sono solo di riferimento)

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

E un'equazione i cui primi sei risultati sono: 3, 6, 7, 10, 21, 47

Produrrebbe la parola "CIAO!" dalla matrice sopra.


Buona idea - Immagino che potresti migliorarlo ulteriormente utilizzando caratteri non stampabili nell'array ...
Thomi

4

Sono d'accordo con @Checkers, il tuo eseguibile può essere decodificato.

Un modo leggermente migliore è crearlo dinamicamente, ad esempio:

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

È vero, questo evita che la stringa venga rivelata durante la ricerca nel file binario. Tuttavia, la tua stringa è ancora residente nella memoria .. La tua soluzione è probabilmente abbastanza buona, per quello che sto facendo.
Thomi

@Thomi, puoi, ovviamente, distruggerlo non appena hai finito. Tuttavia, non è il modo migliore per gestire stringhe sensibili .
Nick Dandoulakis

... poiché la sua distruzione non garantisce che la memoria venga riutilizzata immediatamente.
Thomi

4

Ovviamente, l'archiviazione di dati privati ​​nel software fornito all'utente è sempre un rischio. Qualsiasi ingegnere sufficientemente istruito (e dedicato) potrebbe decodificare i dati.

Detto questo, spesso puoi rendere le cose abbastanza sicure alzando la barriera che le persone devono superare per rivelare i tuoi dati privati. Di solito è un buon compromesso.

Nel tuo caso, potresti ingombrare le tue stringhe con dati non stampabili e quindi decodificarli in fase di esecuzione utilizzando una semplice funzione di supporto, come questa:

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

Ho creato un semplice strumento di crittografia per le stringhe, può generare automaticamente stringhe crittografate e ha alcune opzioni extra per farlo, alcuni esempi:

Stringa come variabile globale:

// 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;

Come stringa Unicode con ciclo di decrittografia:

// 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;

Stringa come variabile globale:

// 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;

1
Nah, ho usato il sito stringencrypt.com per fare il lavoro. Ha esempi per C / C ++ stringencrypt.com/c-cpp-encryption che potresti considerare di usarlo per automatizzare la semplice crittografia delle stringhe.
Bartosz Wójcik

2

Un po 'dipendente da ciò che stai cercando di proteggere, come sottolinea Josperry. Per esperienza, direi che se fa parte di qualche schema di licenza per proteggere il tuo software, non preoccuparti. Alla fine lo decodificheranno. Usa semplicemente un codice semplice come ROT-13 per proteggerlo da attacchi semplici (linea che corre su di esso con stringhe). Se è per proteggere i dati sensibili degli utenti, mi chiedo se proteggere quei dati con una chiave privata archiviata localmente sia una mossa saggia. Ancora una volta si tratta di ciò che stai cercando di proteggere.

EDIT: Se hai intenzione di farlo, una combinazione di tecniche che Chris sottolinea sarà di gran lunga migliore di rot13.


2

Come è stato detto prima, non c'è modo di proteggere completamente la tua corda. Ma ci sono modi per proteggerlo con una ragionevole sicurezza.

Quando ho dovuto farlo, ho inserito una stringa dall'aspetto innocente nel codice (un avviso di copyright, ad esempio, o un prompt utente falso o qualsiasi altra cosa che non sarà cambiata da qualcuno che aggiusta codice non correlato), crittografata usando se stessa come chiave, l'ha hashing (aggiungendo un po 'di sale) e ha utilizzato il risultato come chiave per crittografare ciò che volevo crittografare.

Ovviamente questo potrebbe essere violato, ma ci vuole un hacker determinato per farlo.


Buona idea: un'altra forma di oscurità è usare una stringa che è ancora ragionevolmente forte (lunga, punteggiatura e tutto quel jazz) ma ovviamente non sembra una password.
Thomi

1

Se utilizzi un utente Windows DPAPI, http://msdn.microsoft.com/en-us/library/ms995355.aspx

Come detto in un post precedente, se sei su Mac usa il portachiavi.

Fondamentalmente tutte queste idee carine su come memorizzare la tua chiave privata all'interno del tuo binario sono sufficientemente scadenti dal punto di vista della sicurezza che non dovresti farle. Chiunque riceva la tua chiave privata è un grosso problema, non tenerla all'interno del tuo programma. A seconda di come viene importata la tua app puoi conservare le tue chiavi private su una smart card, su un computer remoto con cui parla il tuo codice oppure puoi fare ciò che fa la maggior parte delle persone e tenerlo in un posto molto sicuro sul computer locale (la "chiave store "che è una specie di strano registro sicuro) che è protetto dai permessi e da tutta la forza del tuo sistema operativo.

Questo è un problema risolto e la risposta NON è tenere la chiave all'interno del programma :)


1

Prova questo . Il codice sorgente spiega come crittografare e decrittografare al volo tutte le stringhe in un determinato progetto Visual Studio c ++.


1

Un metodo che ho provato di recente è:

  1. Prendi l'hash (SHA256) dei dati privati ​​e popolalo nel codice come part1
  2. Prendi lo XOR dei dati privati ​​e il relativo hash e popolalo nel codice come part2
  3. Popolare i dati: non archiviarli come char str [], ma popolarli in fase di esecuzione utilizzando le istruzioni di assegnazione (come mostrato nella macro di seguito)
  4. Ora, genera i dati privati ​​in fase di esecuzione prendendo lo XOR di part1epart2
  5. Passaggio aggiuntivo : calcola l'hash dei dati generati e confrontalo con part1. Verificherà l'integrità dei dati privati.

MACRO per popolare i dati:

Supponiamo che i dati privati ​​siano di 4 byte. Definiamo per esso una macro che salva i dati con le istruzioni di assegnazione in un ordine casuale.

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

Ora usa questa macro nel codice in cui devi salvare part1e part2, come segue:

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

1

C'è un (molto leggero) progetto offuscato di sola intestazione realizzato da adamyaxley che funziona perfettamente. Si basa su funzioni lambda e macro e crittografa le stringhe letterali con un codice XOR in fase di compilazione. Se necessario, possiamo cambiare il seme per ogni stringa.

Il codice seguente non memorizzerà la stringa "hello world" nel file binario compilato.

#include "obfuscate.h"

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

Ho provato con c ++ 17 e visual studio 2019, controllo tramite IDA e confermo che la stringa è nascosta. Un prezioso vantaggio rispetto ad ADVobfuscator è che è convertibile in uno std :: string (pur essendo ancora nascosto nel binario compilato):

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

0

Invece di memorizzare la chiave privata nel tuo eseguibile, potresti richiederla all'utente e memorizzarla tramite un gestore di password esterno , qualcosa di simile a Accesso portachiavi di Mac OS X.


beh sì e no .. normalmente sarei d'accordo con te, ma in questo caso sto cercando di nasconderlo all'utente del software, quindi memorizzarlo in un sistema esterno non è una grande idea (molti sistemi portachiavi possono esporre le password come testo normale agli utenti con adeguata autorizzazione). Il software portachiavi è ottimo per le password degli utenti, ma non così eccezionale per le chiavi di crittografia delle applicazioni.
Thomi

se ti sembra insicuro (anche se dati i tuoi chiarimenti forse è adeguato) potresti combinare un portachiavi e qualcosa di hardcoded :)

0

Dipende dal contesto, ma puoi semplicemente memorizzare l' hash della chiave più un salt (stringa costante, facile da oscurare).

Quindi quando (se) l'utente inserisce la chiave, aggiungi il sale , calcola l' hash e confronta.

Il sale probabilmente non è necessario in questo caso, interrompe un attacco di dizionario a forza bruta se l'hash può essere isolato (anche una ricerca su Google ha funzionato).

Un hacker deve solo inserire un'istruzione jmp da qualche parte per aggirare l'intero lotto, ma è piuttosto più complicato di una semplice ricerca di testo.


Questa è una chiave di crittografia, non un hash della password. Ho bisogno della chiave effettiva per codificare e decodificare i dati. L'utente non vede mai la chiave, non viene mai memorizzata al di fuori del binario.
Thomi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.