Najlepsze praktyki bezpieczeństwa: Symmetric Encryption with AES in Java and Android

Patrick Favre-Bulle
Jan 6, 2018 – 11 min read

W tym artykule przybliżę Ci zaawansowany standard szyfrowania (AES), wspólne tryby blokowe, dlaczego potrzebujesz paddingu i wektorów inicjalizacyjnych oraz jak chronić swoje dane przed modyfikacją. Na koniec pokażę jak łatwo zaimplementować to w Javie unikając większości problemów związanych z bezpieczeństwem.

Co każdy Inżynier Oprogramowania powinien wiedzieć o AES

AES, znany również pod swoją oryginalną nazwą Rijndael, został wybrany przez NIST w 2000 roku, aby znaleźć następcę dla datowanego Data Encryption Standard(DES). AES jest szyfrem blokowym, co oznacza, że szyfrowanie odbywa się na grupach bitów o stałej długości. W naszym przypadku algorytm definiuje 128 bitowe bloki. AES obsługuje klucze o długościach 128, 192 i 256 bitów.

Każdy blok przechodzi przez wiele cykli transformacji. Pominę tutaj szczegóły algorytmu, ale zainteresowanych czytelników odsyłam do artykułu w Wikipedii na temat AES. Istotne jest to, że długość klucza nie wpływa na wielkość bloku, ale na liczbę powtórzeń rund transformacji (128 bitowy klucz to 10 cykli, 256 bitowy to 14)

Do maja 2009 roku jedynymi udanymi opublikowanymi atakami na pełny AES były ataki side-channel na pewne specyficzne implementacje. (Źródło)

Chcemy zaszyfrować więcej niż jeden blok?

Więc AES zaszyfruje tylko 128 bitów danych, ale jeśli chcemy zaszyfrować całe wiadomości musimy wybrać tryb blokowy, w którym wiele bloków może być zaszyfrowanych do jednego tekstu szyfrującego. Najprostszym trybem blokowym jest Electronic Codebook lub ECB. Używa on tego samego niezmienionego klucza na każdym bloku, jak poniżej:

Image from. Wikpedia

Jest to szczególnie złe, ponieważ identyczne bloki plaintext są szyfrowane do identycznych bloków ciphertext.

Obraz zaszyfrowany w trybie ECB tryb blokowy ujawnia wzory oryginału (spróbuj sam)

Pamiętaj, aby nigdy nie wybierać tego trybu, chyba że szyfrujesz tylko dane mniejsze niż 128 bitów. Niestety nadal jest on często nadużywany, ponieważ nie wymaga podania wektora początkowego (więcej o tym później) i dlatego wydaje się być łatwiejszy w obsłudze dla programisty.

Jedna sprawa musi być jednak rozwiązana z trybami blokowymi: co się stanie, jeśli ostatni blok nie będzie miał dokładnie 128 bitów? Tu właśnie w grę wchodzi padding, czyli wypełnianie brakujących bitów bloku. Najprostszy z nich po prostu wypełnia brakujące bity zerami. Wybór paddingu w AES nie ma praktycznie żadnego wpływu na bezpieczeństwo.

Cipher Block Chaining (CBC)

Jakie są więc alternatywy dla ECB? Jedną z nich jest CBC, który XOR-uje bieżący blok tekstu jawnego z poprzednim blokiem szyfrogramu. W ten sposób, każdy blok szyfrogramu zależy od wszystkich bloków tekstu jawnego przetwarzanych do tego momentu. Używając tego samego obrazu co poprzednio, rezultatem byłby szum nie do odróżnienia od losowych danych:

Obraz zaszyfrowany w trybie blokowym CBC wygląda losowo

Co więc z pierwszym blokiem? Najprostszym sposobem jest użycie bloku pełnego np. zer, ale wtedy każde szyfrowanie z tym samym kluczem i tekstem jawnym skutkowałoby tym samym szyfrogramem. Ponadto, jeśli ponownie użyjesz tego samego klucza dla różnych plaintextów, ułatwiłoby to odzyskanie klucza. Lepszym sposobem jest użycie losowego wektora inicjalizacyjnego (IV). Jest to po prostu wymyślne słowo dla losowych danych, które jest o wielkości jednego bloku (128 bit). Pomyśl o tym jak o soli szyfrowania, to znaczy, IV może być publiczny, powinien być losowy i używany tylko jeden raz. Należy jednak pamiętać, że nieznajomość IV utrudni tylko deszyfrowanie pierwszego bloku, ponieważ CBC XORs szyfrogramu, a nie tekstu jawnego poprzedniego bloku.

Podczas przesyłania lub przechowywania danych często dodaje się IV do rzeczywistej wiadomości szyfrującej. Jeśli jesteś zainteresowany jak poprawnie używać AES-CBC sprawdź część 2 tej serii.

Tryb licznika (CTR)

Inną opcją jest użycie trybu CTR. Ten tryb blokowy jest interesujący, ponieważ zmienia on szyfr blokowy w szyfr strumieniowy, co oznacza, że nie jest wymagane padding. W swojej podstawowej formie wszystkie bloki są numerowane od 0 do n. Każdy blok będzie teraz zaszyfrowany kluczem, IV (zwanym tutaj nonce) oraz wartością licznika.

Image from Wikpedia

Zaletą jest, w przeciwieństwie do CBC, szyfrowanie może być wykonywane równolegle i wszystkie bloki są zależne od IV, nie tylko pierwszy. Dużym zastrzeżeniem jest to, że IV nigdy nie może być ponownie użyty z tym samym kluczem, ponieważ atakujący może trywialnie obliczyć używany klucz na tej podstawie.

Czy mogę być pewien, że nikt nie zmienił mojej wiadomości?

Trudna prawda: szyfrowanie nie chroni automatycznie przed modyfikacją danych. W rzeczywistości jest to dość powszechny atak. Przeczytaj tutaj o bardziej szczegółowej dyskusji na ten temat.

Co więc możemy zrobić? Po prostu dodajemy kod uwierzytelniania wiadomości (MAC) do zaszyfrowanej wiadomości. MAC jest podobny do podpisu cyfrowego, z tą różnicą, że klucz weryfikujący i uwierzytelniający są praktycznie takie same. Istnieją różne warianty tej metody, tryb, który jest zalecany przez większość badaczy nazywa się Encrypt-then-Mac. Oznacza to, że po zaszyfrowaniu na tekście szyfrującym obliczany jest MAC, który jest następnie dołączany. Zazwyczaj używa się HMAC (Hash-based message authentication code) jako typ MAC.

Więc teraz zaczyna się robić skomplikowanie. Dla integralności/autentyczności musimy wybrać algorytm MAC, wybrać tryb znacznika szyfrowania, obliczyć mac i dołączyć go. Jest to również powolne, ponieważ cała wiadomość musi być przetwarzana dwukrotnie. Druga strona musi zrobić to samo, ale dla deszyfrowania i weryfikacji.

Uwierzytelnione szyfrowanie z GCM

Czy nie byłoby wspaniale, gdyby istniały tryby, które załatwiałyby wszystkie sprawy związane z uwierzytelnianiem za Ciebie? Na szczęście istnieje coś takiego jak szyfrowanie uwierzytelnione, które jednocześnie zapewnia poufność, integralność i autentyczność danych. Jeden z najpopularniejszych trybów blokowych, który to wspiera nazywa się Galois/Counter Mode lub w skrócie GCM (jest on np. również dostępny jako zestaw szyfrów w TLS v1.2)

GCM jest w zasadzie trybem CTR, który również oblicza znacznik uwierzytelniania sekwencyjnie podczas szyfrowania. Ten znacznik uwierzytelniający jest następnie zwykle dołączany do tekstu szyfrującego. Jego rozmiar jest ważną właściwością bezpieczeństwa, więc powinien mieć długość co najmniej 128 bitów.

Możliwe jest również uwierzytelnienie dodatkowych informacji, które nie są zawarte w tekście jawnym. Dane te nazywane są danymi skojarzonymi. Dlaczego jest to przydatne? Na przykład zaszyfrowane dane mają meta-właściwość, datę utworzenia, która jest używana do sprawdzenia, czy zawartość musi być ponownie zaszyfrowana. Atakujący mógłby teraz trywialnie zmienić datę utworzenia, ale jeśli zostanie ona dodana jako dane stowarzyszone, GCM zweryfikuje również tę informację i rozpozna zmianę.

Gorąca dyskusja: Jakiego rozmiaru klucza użyć?

A więc intuicja mówi: im większy tym lepszy – oczywiste jest, że trudniej jest brute forcingować 256 bitową wartość losową niż 128 bitową. Z naszym obecnym zrozumieniem, brute forcing przez wszystkie wartości 128 bitowego słowa wymagałby astronomicznej ilości energii, nierealistycznej dla nikogo w rozsądnych czasach (patrząc na Ciebie, NSA). Tak więc decyzja jest w zasadzie pomiędzy nieskończonością a nieskończonością razy 2¹²⁸.

AES faktycznie ma trzy różne rozmiary klucza, ponieważ został wybrany jako federalny algorytm USA, który może być używany w różnych obszarach pod kontrolą rządu federalnego USA. (…) Tak więc wspaniałe wojskowe mózgi wpadły na pomysł, że powinny istnieć trzy „poziomy bezpieczeństwa”, tak aby najważniejsze sekrety były szyfrowane ciężkimi metodami, na jakie zasługiwały, ale dane o niższej wartości taktycznej mogły być szyfrowane bardziej praktycznymi, choć słabszymi algorytmami. (…) NIST postanowił więc formalnie przestrzegać przepisów (poprosić o trzy wielkości kluczy), ale jednocześnie postąpić mądrze (najniższy poziom musiał być nie do złamania przy użyciu przewidywalnej technologii)(Źródło)

Teza jest następująca: wiadomość zaszyfrowana AES prawdopodobnie nie zostanie złamana przez brute forcing klucza, ale przez inne, mniej kosztowne ataki (obecnie nieznane). Ataki te będą tak samo szkodliwe dla trybu klucza 128 bitowego jak dla trybu 256 bitowego, więc wybór większego rozmiaru klucza nie pomaga w tym przypadku.

Więc w zasadzie 128 bitowy klucz jest wystarczającym zabezpieczeniem dla większości przypadków użycia z wyjątkiem ochrony komputerów kwantowych. Ponadto użycie 128 bitowego klucza szyfruje szybciej niż 256 bitowego, a harmonogram kluczy dla 128 bitowych kluczy wydaje się być lepiej zabezpieczony przed atakami typu related-key (jednak jest to nieistotne dla większości zastosowań w świecie rzeczywistym).

Jako uwaga dodatkowa: ataki bocznokanałowe

Ataki bocznokanałowe są atakami, których celem jest wykorzystanie problemów specyficznych dla pewnych implementacji. Same schematy szyfrów nie mogą być z natury przed nimi chronione. Proste implementacje AES mogą być podatne między innymi na ataki czasowe i buforowanie.

Jako bardzo podstawowy przykład: prosty algorytm, który jest podatny na ataki czasowe to metoda equals() porównująca dwie tajne tablice bajtów. Jeśli equals() ma szybki powrót, co oznacza, że po pierwszej parze bajtów, które nie pasują, kończy pętlę, atakujący może zmierzyć czas, jaki zajmuje equals() do końca i może zgadywać bajt po bajcie, aż wszystkie będą pasować.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *