Zaszyfrować – jak to łatwo powiedzieć

Urządzenia mobilne zyskują coraz większą popularność zarówno wśród użytkowników, jak i autorów aplikacji, także tych “finansowych”. Coraz więcej banków ma w swojej ofercie aplikacje dla urządzeń mobilnych, za pomocą których klienci mogą korzystać z usług swojego banku. Nie mam tu na myśli specjalnych wersji “normalnego” systemu bankowości internetowej w wersji przeznaczonej dla urządzeń mobilnych (w domyśle – inny layout, pewne ograniczenia funkcjonalne, itp.), ale specjalnie przygotowane aplikacje, które są przede wszystkim wygodniejsze dla klienta w użytkowaniu. Często aplikacje takie realizują wyłącznie GUI, cała istotna logika znajduje się po stronie banku, który udostępnia odpowiednie usługi (web service). Problem pojawia się, gdy taka aplikacja zechce przechowywać pewne dane lokalnie, na urządzeniu. Czy jest w stanie przechowywać je bezpiecznie?

Bezpiecznie, czyli jak?

Właściwie co to znaczy “przechowywać bezpiecznie”? Jakie są zagrożenia? Jak wygląda threat model nawet nie tyle dla samych aplikacji mobilnych, co dla urządzeń mobilnych? Do tego tematu być może w przyszłości wrócę, tym razem ograniczę się do jednego konkretnego scenariusza: załóżmy, że atakujący ma nieograniczony dostęp do urządzenia i danych na nim przechowywanych.

W świecie idealnie kulistych kurczaków (teoria) wszystko jest proste...

Jak aplikacja może chronić swoje dane w takim scenariuszu? Odpowiedź wydaje się oczywista – dane te powinny zostać zaszyfrowane. Problem w tym, że łatwiej jest to powiedzieć, niż zrobić. To znaczy samo szyfrowanie jest proste. Jeśli wzięlibyśmy pod rozwagę Android, to aplikacje w nim działające napisane są w języku Java, a tam wystarczy zajrzeć do pakietów javax.crypto oraz java.security by znaleźć wszystko co potrzeba, by dane porządnie zaszyfrować. Jest tylko jeden mały , ale przy okazji kluczowy problem – klucze.

...ale ta wredna praktyka wszystko psuje!

No właśnie, nie chodzi przecież tylko o to, by dane rozszyfrować. Aby cała zabawa miała sens, musi istnieć możliwość ich późniejszego odszyfrowania. A to oznacza, że aplikacja musi dysponować kluczem użytym do szyfrowania danych lub być w stanie ten klucz wygenerować. Przechowywanie klucza na urządzeniu nie jest pomysłem dobrym – przypominam, że atakujący dysponuje pełnym dostępem do urządzenia i danych na nim przechowywanych. Skoro tak, będzie w stanie odzyskać klucz, a następnie dane odszyfrować. Przechowywanie klucza w postaci zaszyfrowanej problemu oczywiście nie rozwiązuje, bo w jakiś sposób trzeba przechowywać/generować klucz wykorzystany do szyfrowania klucza. Pozostaje więc chyba tylko generowanie kluczy kryptograficznych w trakcie działania aplikacji. Tylko na jakiej podstawie?

Szczerze mówiąc autorzy aplikacji mają niewielki wybór – klucz będzie generowany na podstawie danych pochodzących od użytkownika. Tylko jak wiele tych danych użytkownik będzie w stanie dostarczyć? Interfejs urządzeń mobilnych nie nadaje się do tego, by w wygodny sposób użytkownik mógł wprowadzić długie i złożone hasło. Może być (i bywa) tak, że klucz jest generowany na podstawie kodu PIN o długości np. 6 cyfr. W efekcie szyfrowanie jest iluzoryczne...

PIN o długości 6 cyfr musi być jednoznacznie przeprowadzany w klucz po to, by raz zaszyfrowane dane udało się odszyfrować. 6 cyfr to tylko 1 000 000 kombinacji. Daje to jakieś 20 bitów entropii, co jest śmieszną wartością przy kluczach o długości 128 lub 256 bitów. Sprawdzenie wszystkich możliwych kluczy w takim wypadku nie jest żadnym problemem.

Czy aplikacja może się bronić przed atakiem atakiem brute force? Teoretycznie tak, może przecież po kilku nieudanych próbach podania PIN usunąć wszystkie dane. Tylko, że szanujący się atakujący nie będzie próbował tych pinów wpisywać ręcznie. I nie będzie ich sprawdzał za pośrednictwem aplikacji. Dokładnie z tego samego powodu nie ma sensu wbudowywać w narzędzia typu KeePass opcji “samozniszczenia” bazy po wielokrotnym podaniu złego hasła. Atakujący albo nie będzie korzystał z KeePass, albo po prostu oryginalną bazę najpierw skopiuje, a później będzie atakował jej kopie.

A może by tak TPM?

Rozwiązanie problemu, o którym piszę, teoretycznie mogłoby być proste. Wystarczy jeśli w urządzeniu pojawiłby się moduł TPM i aplikacje uzyskały odpowiednie API do dostępu do niego. W tym wypadku aplikacja mogłaby generować porządny klucz i wykorzystywać go w operacji szyfrowania. Sam klucz mógłby być przechowywany w postaci szyfrowanej, a jego odszyfrowanie przy uruchamianiu aplikacji odbywałoby się z wykorzystaniem modułu TPM, który przed atakiem brute force już może się bronić dość skutecznie. Tak, na moduły TPM też były demonstrowane ataki (fizyczne), ale umówmy się, że stopień złożoności takiego ataku jest nieco większy, niż sprawdzenie wszystkich możliwych kluczy z pewnej niezbyt dużej przestrzeni...

Oryginał tego wpisu dostępny jest pod adresem Zaszyfrować – jak to łatwo powiedzieć

Autor: Paweł Goleń