W poprzednim wpisie dotyczącym integralności pojawił się problem sprawdzenia, czy wiadomość pochodzi z oryginalnego źródła. Innymi słowy, czy została wysłana przez podmiot, który traktujemy jako zaufany. Aby to stwierdzić, musimy zastosować uwierzytelnianie (ang. authentication), które jest procesem polegającym na upewnieniu się, że deklarowana cecha danego podmiotu jest prawdziwa. W kryptografii realizowane jest ono między innymi przez mechanizm podpisu cyfrowego oraz kodu uwierzytelniania wiadomości (ang. message authentication code, MAC). Poniższy wpis omawia dokładniej drugą z tych technik.
Kod uwierzytelniania wiadomości
MAC jest funkcją, która przyjmuje na wejściu wiadomość oraz klucz, a wynikiem jej działania jest pewna wartość, zależąca zarówno od przetwarzanej wiadomości jak i klucza. Oznacza to, że wyznaczyć dla danej wiadomości mogą go tylko jednostki, które posiadają klucz. Zatem fundamentem bezpieczeństwa jest zapewnienie tajności klucza. Musi on zostać uzgodniony lub wymieniony takim sposobem, który zagwarantuje jego poufność. O tym jak zrealizować takie wymaganie napiszę w przyszłości.
Jak używać MAC
Wiadomość, którą zabezpieczamy za pomocą MAC (potocznie mówi się o macowaniu komunikatów) przesyłana jest do odbiorcy w formie jawnej z dołączoną wartością kodu uwierzytelniania wiadomości wyznaczoną na podstawie klucza. Odbiorca samodzielnie oblicza MAC na podstawie otrzymanej wiadomości oraz przechowywanego klucza, a następnie dokonuje porównania z otrzymaną wartością. Jeżeli są one równe, oznacza to, że wiadomość została przygotowana przez jednostkę posiadającą klucz i może trafić do dalszego przetwarzania. Jeżeli kody się różnią, to zakłócona została przekazywana wiadomość lub wyliczono MAC na podstawie innego klucza. Bez względu na przyczynę, takiej wiadomości nie przetwarzamy dalej, ponieważ nie jesteśmy w stanie zweryfikować jej oryginalności. Można powiedzieć, że nadawca stosuje klucz w celu podpisania wiadomości, natomiast odbiorca używa tego klucza w celu jej weryfikacji.
To nie wystarczy, aby zapewnić bezpieczeństwo rozwiązania wykorzystującego kody uwierzytelniania wiadomości. Atakujący może przechwycić wiadomość wraz z wyliczonym kodem i... nadać ją ponownie bez żadnych zmian. Taki atak nazywany jest atakiem powtórzeniowym. Aby go uniknąć nie powinno dopuszczać się do umożliwienia przetwarzania w systemie tych samych komunikatów. Każdy z nich powinien być inny, co można osiągnąć poprzez dodanie np. unikalnego identyfikatora wiadomości. Może to być rosnący licznik wyrażony poprzez kolejny numer komunikatu lub aktualny czas. W takim przypadku system nie przetworzy komunikatów oznaczonych wartością mniejszą lub równą niż ostatnio przetworzona. Komunikatom można też nadawać losowe, niepowtarzalne identyfikatory. Przy takim rozwiązaniu niezbędne jest przechowywanie bazy identyfikatorów wiadomości już przetworzonych. Można również zastosować jednorazowe klucze dla takich samych komunikatów. Implementacja takiego rozwiązania jest bardziej skomplikowana, ponieważ klucze wcześniej trzeba bezpiecznie wymienić lub uzgodnić lub zastosować odpowiednie techniki przygotowywania takich kluczy.
Atakujący może wykorzystać powtórnie przechwyconą informację, nie tylko wysyłając ją ponownie do początkowego odbiorcy, ale również jako odpowiedź do nadawcy. Tego scenariusza można uniknąć, stosując techniki omówione powyżej lub używając dwóch różnych kluczy do wyliczania MAC, zależne od tego, która ze stron jest nadawcą komunikatu.
Algorytm HMAC
Do wyliczenia kodu uwierzytelniania wiadomości możemy wykorzystać poznane już funkcje skrótu. Korzystając z ich właściwości można pokusić się o wyliczanie MAC na podstawie $$MAC_K (M) = H (K | M)$$ (operator $$|$$ oznacza tu połączenie dwóch ciągów). Jednak nie znając szczegółów działania zastosowanej funkcji skrótu, możemy w ten sposób umożliwić atakującemu wyliczanie MAC bez znajomości klucza na podstawie wiadomości M. Wynika to z faktu, że wiele funkcji skrótu, w tym rodzina SHA-2, działa w sposób iteracyjny. Oznacza to, że wyliczona wartość dla danego $$M$$ jest bazą do dalszego wyliczania skrótu dla wiadomości $$M | M_x$$. Ponieważ atakujący może przechwycić wiadomość $$M$$ wraz z $$H (K | M)$$ to może zacząć wznaczać inne wiadomości $$M | M_x$$ i wyznaczać $$H (K | M | M_x)$$ na podstawie znanego $$H (K | M)$$. W ten sposób, bez znajomości tajnego klucza, może manipulować wiadomościami. Nie wszystkie funkcje skrótu są podatne na ten typ ataku. Należy do nich na przykład SHA-3.
Zamiast wymyślać własne rozwiązanie, należy zastosować dedykowany do tego celu algorytm HMAC (ang. Keyed-Hash Message Authentication Code). Bazuje on na wykorzystaniu funkcji skrótu, ale w sposób, który uniemożliwia zastosowanie ataku opisanego powyżej. HMAC na podstawie przekazanego klucza tworzy dwa różne klucze. Następnie wylicza wartość MAC jako $$HMAC_K (M) = H(K_1 | H (K_2 | M))$$. Algorytm opisany jest standardzie FIPS 198-1 oraz w RFC 2140. Aktualnie dla HMAC zalecane jest stosowanie klucza o długości co najmniej 112 bitów oraz używanie funkcji skrótu co najmniej SHA-256, która wyznacza 256-cio bitową wartość MAC.
W Javie wartość funkcji MAC możemy wyznaczyć, używając fabryki Mac
. Nazwa wybranego sposobu obliczania MAC przekazywana jest jako ciąg znaków. Podobnie jak inne algorytmy znajdziemy je w dokumencie Standard Algorithm Name Documentation.
Efektem działania poniższego programu będzie obliczenie wartości funkcji HMAC na podstawie SHA-256 dla przykładowej wiadomości oraz klucza.
1import javax.crypto.Mac; 2import javax.crypto.spec.SecretKeySpec; 3 4public class MACTest { 5 public static void main(String[] args) throws Exception { 6 byte[] data = "abcdefghijklmnoprstuwxyz1234567890!".getBytes(); 7 byte[] key = "to-jest-tajny-klucz".getBytes(); 8 9 SecretKeySpec macKey = new SecretKeySpec(key, "HmacSHA256"); 10 Mac mac = Mac.getInstance("HmacSHA256"); 11 mac.init(macKey); 12 byte hmac[] = mac.doFinal(data); 13 } 14}
Powyższy przykład ma podstawową wadę. Tajny klucz przechowujemy w kodzie źródłowym aplikacji. Jest to złe rozwiązanie, ponieważ tym sposobem nie zapewnimy jego bezpieczeństwa. Wymagałoby to utrzymania w tajności całego kodu źródłowego oraz jego wersji binarnej. Problemy związane z bezpiecznym przechowywaniem kluczy pojawią się również w następnych wpisach i będą wymagały dobrego rozwiązania.
Podsumowanie
Kody uwierzytelniania wiadomości zapewniają realizację usługi integralności oraz uwierzytelnienia przesłanej informacji. Należy ich jednak używać w odpowiedni sposób. W szczególności nie powinno się wymyślać własnych algorytmów oraz prawidłowo przygotować protokół komunikacyjny, aby uniemożliwić typowe ataki pozwalające na ponowne wykorzystanie przechwyconej informacji lub jej wydłużenie przez nieuprawnionego nadawcę. Zabezpieczana wiadomość jest przesyłana w formie jawnej, ale mamy pewność, że nikt jej nie zmanipulował. Jeśli chcemy zapewnić jej tajność, musimy zaimplementować kolejną usługę, którą jest poufność. W kolejnym wpisie przedstawię algorytmy, które pozwolą nam osiągnąć ten cel.