Muszę przechowywać poufne informacje (symetryczny klucz szyfrowania, który chcę zachować dla siebie) w mojej aplikacji C ++. Prostym podejściem jest zrobienie tego:
std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";
Jednak uruchomienie aplikacji przez strings
proces (lub inny, który wyodrębnia ciągi z aplikacji binarnej) ujawni powyższy ciąg.
Jakie techniki należy zastosować, aby ukryć takie wrażliwe dane?
Edytować:
OK, więc prawie wszyscy powiedzieliście: „Twój plik wykonywalny może zostać poddany inżynierii wstecznej” - oczywiście! To moje zwierzę domowe irytu, więc mam zamiar trochę narzekać tutaj:
Dlaczego na 99% (OK, więc może trochę przesadzam) wszystkich pytań związanych z bezpieczeństwem na tej stronie odpowiada torrent „nie ma możliwości stworzenia całkowicie bezpiecznego programu” - to nie jest pomocne odpowiedź! Bezpieczeństwo to przesuwająca się skala pomiędzy doskonałą użytecznością i brakiem bezpieczeństwa z jednej strony, a doskonałym bezpieczeństwem, ale brakiem użyteczności z drugiej.
Chodzi o to, że wybierasz swoją pozycję na tej ruchomej skali w zależności od tego, co próbujesz zrobić i środowiska, w którym będzie działać Twoje oprogramowanie. Nie piszę aplikacji do instalacji wojskowej, piszę aplikację na domowy komputer . Muszę zaszyfrować dane w niezaufanej sieci za pomocą wcześniej znanego klucza szyfrowania. W takich przypadkach „bezpieczeństwo przez zaciemnienie” jest prawdopodobnie wystarczające! Jasne, ktoś z wystarczającą ilością czasu, energii i umiejętności mógłby odtworzyć plik binarny i znaleźć hasło, ale wiecie co? Nie obchodzi mnie:
Czas potrzebny na wdrożenie bezpiecznego systemu na najwyższym poziomie jest droższy niż utrata sprzedaży z powodu pękniętych wersji (nie to, że faktycznie to sprzedaję, ale rozumiesz, o co mi chodzi). Ten modny trend w programowaniu wśród nowych programistów „pozwala robić to w najlepszy możliwy sposób” jest co najmniej głupi.
Dziękuję za poświęcenie czasu na odpowiedź na to pytanie - okazały się one bardzo pomocne. Niestety mogę zaakceptować tylko jedną odpowiedź, ale przegłosowałem wszystkie przydatne odpowiedzi.
Odpowiedzi:
Zasadniczo, każdy z dostępem do swojego programu i debugger może i będzie znaleźć klucz w aplikacji, jeśli chcą.
Ale jeśli chcesz się tylko upewnić, że klucz nie pojawia się podczas uruchamiania
strings
na twoim pliku binarnym, możesz na przykład upewnić się, że klucz nie znajduje się w zakresie drukowalnym.Ukryty klucz z XOR
Na przykład możesz użyć XOR do podzielenia klucza na dwie tablice bajtowe:
Jeśli utworzysz klucz1 o tej samej długości bajtów,
key
jaką możesz użyć (całkowicie) losowych wartości bajtów, a następnie obliczyszkey2
:key1[n] = crypto_grade_random_number(0..255) key2[n] = key[n] XOR key1[n]
Można to zrobić w środowisku kompilacji, a następnie przechowywać tylko
key1
ikey2
w aplikacji.Ochrona twojego pliku binarnego
Innym podejściem jest użycie narzędzia do ochrony pliku binarnego. Na przykład istnieje kilka narzędzi bezpieczeństwa, które mogą upewnić się, że plik binarny jest zaciemniony i uruchamia maszynę wirtualną, na której działa. Utrudnia to debugowanie, a także jest konwencjonalnym sposobem ochrony wielu bezpiecznych aplikacji klasy komercyjnej (również, niestety, złośliwego oprogramowania).
Jednym z najlepszych narzędzi jest Themida , która świetnie chroni Twoje pliki binarne. Jest często używany przez dobrze znane programy, takie jak Spotify, do ochrony przed inżynierią wsteczną. Posiada funkcje zapobiegające debugowaniu w programach takich jak OllyDbg i Ida Pro.
Istnieje również większa lista, być może nieco przestarzała, narzędzi do ochrony Twojego pliku binarnego .
Niektóre z nich są bezpłatne.
Dopasowanie hasła
Ktoś tutaj omówił haszowanie hasła + sól.
Jeśli chcesz zapisać klucz, aby dopasować go do jakiegoś hasła przesłanego przez użytkownika, powinieneś użyć jednokierunkowej funkcji mieszającej, najlepiej łącząc nazwę użytkownika, hasło i sól. Problem z tym polega jednak na tym, że twoja aplikacja musi znać sól, aby móc wykonać jednokierunkowe i porównać wynikowe hashe. Dlatego nadal musisz przechowywać sól gdzieś w aplikacji. Jednak, jak @Edward wskazuje w komentarzach poniżej, będzie to skutecznie chronić przed atakiem słownikowym przy użyciu np. Tęczowych tabel.
Wreszcie możesz użyć kombinacji wszystkich powyższych technik.
źródło
Przede wszystkim zdaj sobie sprawę, że nie możesz zrobić nic, co powstrzymałoby wystarczająco zdeterminowanego hakera, a takich wokół jest mnóstwo. Ochrona w każdej grze i konsoli w pobliżu jest w końcu pęknięta, więc jest to tylko tymczasowa poprawka.
Są 4 rzeczy, które możesz zrobić, aby zwiększyć swoje szanse na pozostanie w ukryciu.
1) Ukryj w jakiś sposób elementy łańcucha - coś tak oczywistego, jak xoring (operator ^), łańcuch z innym łańcuchem będzie wystarczająco dobry, aby łańcuch nie był możliwy do wyszukania.
2) Podziel ciąg na części - podziel ciąg i rozbijaj jego fragmenty na dziwnie nazwane metody w dziwnych modułach. Nie ułatwiaj przeszukiwania i znajdowania metody zawierającej ciąg. Oczywiście jakaś metoda będzie musiała wywołać wszystkie te bity, ale nadal będzie to trochę trudniejsze.
3) Nigdy nie buduj łańcucha w pamięci - większość hakerów używa narzędzi, które pozwalają im zobaczyć łańcuch w pamięci po zakodowaniu go. Jeśli to możliwe, unikaj tego. Jeśli na przykład wysyłasz klucz do serwera, wysyłaj go znak po znaku, aby nigdy nie było całego ciągu. Oczywiście, jeśli używasz go z czegoś takiego jak kodowanie RSA, jest to trudniejsze.
4) Wykonaj algorytm ad-hoc - do tego wszystkiego dodaj unikalny zwrot akcji lub dwa. Może po prostu dodaj 1 do wszystkiego, co wyprodukujesz, lub wykonaj szyfrowanie dwa razy lub dodaj cukier. To tylko utrudnia hakerowi, który już wie, czego szukać, gdy ktoś używa na przykład waniliowego haszowania md5 lub szyfrowania RSA.
Przede wszystkim upewnij się, że nie jest zbyt ważne, kiedy (i będzie, gdy aplikacja stanie się wystarczająco popularna) zostanie odkryty Twój klucz!
źródło
Strategia, której używałem w przeszłości, polega na tworzeniu tablicy pozornie przypadkowych znaków. Początkowo wstawiasz, a następnie lokalizujesz określone znaki za pomocą procesu algebraicznego, w którym każdy krok od 0 do N daje numer <rozmiar tablicy, która zawiera następny znak w zaciemnionym ciągu. (Ta odpowiedź wydaje się teraz zaciemniona!)
Przykład:
Biorąc pod uwagę tablicę znaków (liczby i myślniki służą wyłącznie jako odniesienie)
0123456789 ---------- ALFHNFELKD LKFKFLEHGT FLKRKLFRFK FJFJJFJ!JL
I równanie, którego pierwszych sześć wyników to: 3, 6, 7, 10, 21, 47
Dałoby słowo „HELLO!” z tablicy powyżej.
źródło
Zgadzam się z @Checkers, twój plik wykonywalny może zostać odtworzony.
Nieco lepszym sposobem jest tworzenie go dynamicznie, na przykład:
std::string myKey = part1() + part2() + ... + partN();
źródło
Oczywiście przechowywanie prywatnych danych w oprogramowaniu wysyłanym do użytkownika jest zawsze ryzykowne. Każdy wystarczająco wykształcony (i oddany) inżynier może odtworzyć dane.
Biorąc to pod uwagę, często można zapewnić wystarczające bezpieczeństwo, podnosząc barierę, którą ludzie muszą pokonać, aby ujawnić swoje prywatne dane. To zwykle dobry kompromis.
W twoim przypadku możesz zaśmiecać swoje ciągi danymi niedrukowalnymi, a następnie zdekodować je w czasie wykonywania za pomocą prostej funkcji pomocniczej, takiej jak ta:
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; // ... }
źródło
Stworzyłem proste narzędzie do szyfrowania ciągów, które może automatycznie generować zaszyfrowane ciągi i ma kilka dodatkowych opcji, aby to zrobić, kilka przykładów:
Ciąg jako zmienna globalna:
// 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;
Jako ciąg znaków Unicode z pętlą deszyfrującą:
// 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;
Ciąg jako zmienna globalna:
// 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;
źródło
W pewnym stopniu zależy od tego, co próbujesz chronić, jak wskazuje Joshperry. Z doświadczenia powiedziałbym, że jeśli jest to część jakiegoś schematu licencjonowania w celu ochrony oprogramowania, nie przejmuj się. W końcu dokonają inżynierii wstecznej. Po prostu użyj prostego szyfru, takiego jak ROT-13, aby zabezpieczyć go przed prostymi atakami (ciągami znaków na nim). Jeśli ma zabezpieczyć wrażliwe dane użytkowników, zastanawiałbym się, czy ochrona tych danych za pomocą klucza prywatnego przechowywanego lokalnie jest mądrym posunięciem. Znowu wszystko sprowadza się do tego, co próbujesz chronić.
EDYCJA: Jeśli masz zamiar to zrobić, połączenie technik, które wskazuje Chris, będzie o wiele lepsze niż rot13.
źródło
Jak zostało powiedziane wcześniej, nie ma sposobu, aby całkowicie chronić swój ciąg. Ale są sposoby, aby go chronić, zapewniając rozsądne bezpieczeństwo.
Kiedy musiałem to zrobić, umieściłem w kodzie jakiś niewinnie wyglądający ciąg znaków (na przykład informacja o prawach autorskich, fałszywy monit użytkownika lub cokolwiek innego, co nie zostanie zmienione przez kogoś, kto naprawi niepowiązany kod), zaszyfrował to używając samego siebie jako klucz, zaszyfrowałem to (dodając trochę soli) i wykorzystałem wynik jako klucz do zaszyfrowania tego, co faktycznie chciałem zaszyfrować.
Oczywiście można to zhakować, ale zrobienie tego wymaga zdeterminowanego hakera.
źródło
Jeśli korzystasz z interfejsu DPAPI użytkownika systemu Windows, http://msdn.microsoft.com/en-us/library/ms995355.aspx
Jak powiedział poprzedni post, jeśli korzystasz z komputera Mac, użyj pęku kluczy.
Zasadniczo wszystkie te urocze pomysły dotyczące przechowywania klucza prywatnego w pliku binarnym są na tyle słabe z punktu widzenia bezpieczeństwa, że nie powinieneś ich robić. Każdy, kto otrzyma Twój klucz prywatny, to wielka sprawa, nie trzymaj go w swoim programie. W zależności od tego, jak zaimportujesz aplikację, możesz przechowywać klucze prywatne na karcie inteligentnej, na zdalnym komputerze, z którym komunikuje się Twój kod lub możesz robić to, co robi większość ludzi i przechowywać je w bardzo bezpiecznym miejscu na komputerze lokalnym (klucz store ”, który jest czymś w rodzaju dziwnego bezpiecznego rejestru), który jest chroniony przez uprawnienia i całą moc systemu operacyjnego.
To jest rozwiązany problem, a odpowiedzią nie jest przechowywanie klucza w programie :)
źródło
Spróbuj tego . Kod źródłowy wyjaśnia, jak szyfrować i odszyfrowywać w locie wszystkie ciągi w danym projekcie C ++ programu Visual Studio.
źródło
Ostatnio wypróbowałem jedną z metod:
part1
part2
part1
ipart2
part1
. Sprawdzi integralność prywatnych danych.MAKRO do wypełniania danych:
Załóżmy, że dane prywatne mają 4 bajty. Definiujemy dla niego makro, które zapisuje dane z instrukcjami przypisania w losowej kolejności.
#define POPULATE_DATA(str, i0, i1, i2, i3)\ {\ char *p = str;\ p[3] = i3;\ p[2] = i2;\ p[0] = i0;\ p[1] = i1;\ }
Teraz użyj tego makra w kodzie, w którym musisz zapisać
part1
ipart2
w następujący sposób:char part1[4] = {0}; char part2[4] = {0}; POPULATE_DATA(part1, 1, 2, 3, 4); POPULATE_DATA(part2, 5, 6, 7, 8);
źródło
Istnieje (bardzo lekki) zaciemniony projekt zawierający tylko nagłówek, stworzony przez adamyaxley, który działa doskonale. Opiera się na funkcjach lambda i makrach i szyfruje łańcuchy tekstowe za pomocą szyfru XOR w czasie kompilacji. W razie potrzeby możemy zmienić ziarno dla każdego sznurka.
Poniższy kod nie zachowa ciągu „hello world” w skompilowanym pliku binarnym.
#include "obfuscate.h" int main() { std::cout << AY_OBFUSCATE("Hello World") << std::endl; return 0; }
Testowałem z C ++ 17 i Visual Studio 2019 i sprawdzam przez IDA i potwierdzam, że ciąg jest ukryty. Jedną cenną zaletą w porównaniu do ADVobfuscator jest to, że można go konwertować na std :: string (będąc nadal ukrytym w skompilowanym pliku binarnym):
std::string var = AY_OBFUSCATE("string");
źródło
Zamiast przechowywać klucz prywatny w swoim pliku wykonywalnym, możesz zażądać go od użytkownika i zapisać go za pomocą zewnętrznego menedżera haseł , czegoś podobnego do Dostępu do pęku kluczy w systemie Mac OS X.
źródło
Zależne od kontekstu, ale możesz po prostu przechowywać skrót klucza i sól (stały ciąg, łatwy do zaciemnienia).
Następnie, gdy (jeśli) użytkownik wprowadzi klucz, dodajesz sól , obliczasz hash i porównujesz.
Sól jest prawdopodobnie niepotrzebne w tym przypadku, to zatrzymuje atak brute-force słownika jeśli hash można wydzielić (a wyszukiwarka Google została również wiedzieć, aby pracy).
Haker nadal musi tylko gdzieś wstawić instrukcję jmp, aby ominąć całość, ale jest to raczej bardziej skomplikowane niż proste wyszukiwanie tekstu.
źródło