Domanda

Devo archiviare informazioni riservate (una chiave di crittografia simmetrica che desidero mantenere privata) nella mia applicazione C++.L'approccio semplice è fare questo:

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

Tuttavia, eseguendo l'applicazione tramite il file strings process (o qualsiasi altro che estrae stringhe da un'app binaria) rivelerà la stringa sopra.

Quali tecniche dovrebbero essere utilizzate per oscurare dati così sensibili?

Modificare:

OK, praticamente lo avete detto tutti "il tuo eseguibile può essere decodificato" - Ovviamente!Questa è una mia seccatura, quindi mi lamenterò un po' qui:

Perché al 99% (OK, forse esagero un po') di tutte le domande relative alla sicurezza su questo sito viene data risposta con un torrente di "non esiste un modo possibile per creare un programma perfettamente sicuro"? Non è una soluzione utile risposta!La sicurezza è una scala mobile tra perfetta usabilità e nessuna sicurezza da un lato, e perfetta sicurezza ma nessuna usabilità dall’altro.

Il punto è che scegli la tua posizione su quella scala mobile a seconda di ciò che 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 nota.In questi casi, la "sicurezza attraverso l'oscurità" è probabilmente sufficiente!Certo, qualcuno con abbastanza tempo, energia e abilità potrebbe decodificare il codice binario e trovare la password, ma indovina un po'?Non mi interessa:

Il tempo che mi occorre per implementare un sistema sicuro di prim'ordine è più costoso della perdita di vendite dovuta alle versioni crackate (non che io lo stia effettivamente vendendo, ma hai capito cosa intendo).Questa tendenza "facciamolo nel miglior modo possibile" nella programmazione tra i nuovi programmatori è a dir poco sciocca.

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.

È stato utile?

Soluzione

Fondamentalmente, chiunque abbia accesso al tuo programma e un debugger può e troveranno la chiave nell'applicazione se lo desiderano.

Ma, se vuoi solo assicurarti che la chiave non venga visualizzata quando esegui strings sul tuo binario, puoi ad esempio assicurarti che la chiave non sia all'interno dell'intervallo stampabile.

Chiave oscura con XOR

Ad esempio, è possibile utilizzare XOR per dividere la chiave in matrici a due byte:

key = key1 XOR key2

Se crei key1 con la stessa lunghezza di byte di key puoi utilizzare (completamente) valori di byte 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 solo key1 e <=> nella tua applicazione.

Protezione del file binario

Un altro approccio è usare uno strumento per proteggere il tuo binario. Ad esempio, esistono diversi strumenti di sicurezza che possono assicurarsi che il file binario sia offuscato e che avvii una macchina virtuale su cui è in esecuzione. Ciò rende difficile il debug (er) ed è anche il modo conventuale di proteggere molte applicazioni sicure di livello commerciale (anche, ahimè, malware).

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

Esiste anche un elenco più ampio, forse in qualche modo obsoleto, di il tuo binario .
Alcuni di essi sono gratuiti.

Corrispondenza password

Qualcuno qui ha discusso di hashing password + salt.

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

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

Altri suggerimenti

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

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

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

2) Dividi la stringa in pezzi - dividi la tua stringa e fai scoppiare frammenti in metodi stranamente nominati in strani moduli. Non semplificare la ricerca e trovare il metodo con la stringa al suo interno. Naturalmente un 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 usa strumenti che permettono loro di vedere la stringa in memoria dopo averla codificata. Se possibile, evitatelo. Se ad esempio stai inviando la chiave a un server, inviala carattere per carattere, quindi l'intera stringa non è mai presente. Naturalmente, se lo stai usando da qualcosa come la codifica RSA, allora questo è più complicato.

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

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

Una strategia che ho usato in passato è quella di creare una serie di personaggi apparentemente casuali. Inizialmente si inserisce e quindi si individuano i caratteri particolari con un processo algebrico in cui ogni passaggio da 0 a N produrrà un numero & Lt; 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

Renderebbe la parola " CIAO! " dall'array sopra.

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

Un modo leggermente migliore è crearlo in modo dinamico, ad esempio:

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

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 decrittazione:

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

Naturalmente, archiviare dati privati ​​nel software spedito all'utente è sempre un rischio.Qualsiasi ingegnere sufficientemente istruito (e dedicato) potrebbe decodificare i dati.

Detto questo, spesso puoi rendere le cose sufficientemente 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 decodificarle 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;
    // ...
}

In qualche modo dipendente da ciò che stai cercando di proteggere come sottolinea joshperry. Per esperienza, direi che se fa parte di uno schema di licenze per proteggere il tuo software, non preoccuparti. Alla fine lo decodificheranno. Basta usare un semplice codice come ROT-13 per proteggerlo da semplici attacchi (linea che scorre stringhe su di esso). Se si tratta di proteggere i dati sensibili degli utenti, mi chiedo se proteggere tali dati con una chiave privata archiviata localmente sia una mossa saggia. Ancora una volta si riduce a ciò che stai cercando di proteggere.

EDIT: se hai intenzione di farlo, allora una combinazione di tecniche che Chris sottolinea sarà molto meglio di rot13.

Come detto prima, non c'è modo di proteggere totalmente la tua stringa. 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 sul copyright, ad esempio, o un prompt utente falso o qualsiasi altra cosa che non verrà modificata da qualcuno che aggiusta il codice non correlato), crittografato che usandosi come chiave, l'hash (aggiungendo un po 'di sale) e ha usato il risultato come chiave per crittografare ciò che volevo effettivamente crittografare.

Naturalmente questo potrebbe essere hackerato, ma ci vuole un hacker determinato per farlo.

Invece di archiviare la chiave privata nel tuo eseguibile, potresti volerla richiedere all'utente e archiviarla per mezzo di un gestore password , qualcosa di simile all'accesso Portachiavi di Mac OS X.

Se utilizzi DPAPI di Windows, http://msdn.microsoft .com / it-it / library / ms995355.aspx

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

Fondamentalmente tutte queste idee carine su come archiviare la tua chiave privata all'interno del tuo binario sono sufficientemente scarse dal punto di vista della sicurezza che non dovresti farle. Chiunque ottenga la tua chiave privata è un grosso problema, non tenerla nel tuo programma. A seconda dell'importazione della tua app, puoi conservare le tue chiavi private su una smart card, su un computer remoto con cui parla il tuo codice o puoi fare quello che fanno la maggior parte delle persone e conservarlo in un posto molto sicuro sul computer locale (il quot; key store " che è un po 'come uno strano registro sicuro) che è protetto dalle autorizzazioni e da tutta la forza del tuo sistema operativo.

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

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

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. Popola dati:Non memorizzarlo come char str[], ma popolarlo in fase di esecuzione utilizzando le istruzioni di assegnazione (come mostrato nella macro seguente)
  4. Ora, genera i dati privati ​​in fase di esecuzione prendendo lo XOR di part1 E part2
  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 una macro per esso 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 part1 E 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);

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

Quindi quando (se) l'utente inserisce la chiave, aggiungi salt , calcola hash e confronti.

Il salt probabilmente non è necessario in questo caso, blocca un attacco con dizionario a forza bruta se l'hash può essere isolato (anche una ricerca di Google è nota per funzionare).

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top