Klucze opakowane sprzętowo

Podobnie jak w przypadku większości oprogramowania do szyfrowania dysków i plików, szyfrowanie pamięci Androida tradycyjnie polega na nieprzetworzonych kluczach szyfrowania dostępnych w pamięci systemowej, aby można było przeprowadzić szyfrowanie. Nawet jeśli szyfrowanie jest wykonywane przez dedykowany sprzęt, a nie oprogramowanie, to oprogramowanie nadal musi zarządzać nieprzetworzonymi kluczami szyfrowania.

Tradycyjnie nie jest to traktowane jako problem, ponieważ klucze nie będą dostępne podczas ataku offline, przed którym ma być zabezpieczane szyfrowanie pamięci masowej. Chcemy jednak zapewnić lepszą ochronę przed innymi typami ataków, takimi jak ataki z zimnego rozruchu czy ataki online, w których przypadku atakujący może uzyskać dostęp do pamięci systemowej bez całkowitego skompromitowania urządzenia.

Aby rozwiązać ten problem, w Androidzie 11 wprowadzono obsługę kluczy zaszyfrowanych sprzętowo, gdzie dostępna jest obsługa sprzętowa. Klucze zaszyfrowane sprzętowo to klucze magazynowe, które są znane w postaci surowej tylko dla dedykowanego sprzętu. Oprogramowanie widzi i działa z tymi kluczami tylko w zaszyfrowanej formie. Sprzęt musi umożliwiać generowanie i importowanie kluczy magazynu, stosowanie kluczy magazynu w formie tymczasowej i długoterminowej, wyprowadzanie kluczy podrzędnych, bezpośrednie programowanie jednego klucza podrzędnego w ramach wbudowanego mechanizmu szyfrowania oraz zwracanie oddzielnego klucza podrzędnego do oprogramowania.

Uwaga: wbudowany mechanizm szyfrowania (wbudowany sprzęt do szyfrowania) odnosi się do sprzętu, który szyfruje/odszyfrowuje dane w trakcie przesyłania do/z urządzenia pamięci masowej. Zwykle jest to host UFS lub kontroler eMMC, który implementuje rozszerzenia szyfrowania zdefiniowane w odpowiedniej specyfikacji JEDEC.

Projektowanie

W tej sekcji przedstawiono projekt funkcji kluczy w ramach oprogramowania sprzętowego, w tym wymagania dotyczące sprzętu. Ta dyskusja koncentruje się na szyfrowaniu plików (FBE), ale rozwiązanie dotyczy też szyfrowania metadanych.

Jednym ze sposobów uniknięcia potrzeby przechowywania nieprzetworzonych kluczy szyfrowania w pamięci systemowej jest przechowywanie ich tylko w kluczowych slotach wbudowanego modułu szyfrowania. Pojawia się jednak kilka problemów:

  • Liczba kluczy szyfrowania może przekroczyć liczbę sekcji kluczy.
  • Wbudowane mechanizmy kryptograficzne zwykle tracą zawartość przedziałów kluczy, gdy kontroler pamięci masowej (zwykle UFS lub eMMC) zostanie zresetowany. Resetowanie kontrolera pamięci jest standardową procedurą naprawy błędów, która jest wykonywana w przypadku wystąpienia określonych typów błędów pamięci. Takie błędy mogą wystąpić w dowolnym momencie. Dlatego, gdy używana jest szyfrowanie w ramach, system operacyjny musi być zawsze gotowy do przeprogramowania slotów kluczy bez udziału użytkownika.
  • Wbudowane silniki kryptograficzne mogą być używane tylko do szyfrowania/odszyfrowywania pełnych bloków danych na dysku. W przypadku FBE oprogramowanie musi jednak mieć możliwość wykonywania innych operacji kryptograficznych, takich jak szyfrowanie nazw plików i wyodrębnianie identyfikatorów kluczy. Oprogramowanie nadal będzie potrzebować dostępu do nieprzetworzonych kluczy FBE, aby wykonywać te inne czynności.

Aby uniknąć tych problemów, klucze magazynu są zamiast tego tworzone jako klucze opakowane sprzętowo, które mogą być rozpakowane i używane tylko przez dedykowany sprzęt. Umożliwia to obsługę nieograniczonej liczby kluczy. Hierarchia kluczy jest dodatkowo modyfikowana i częściowo przenoszona na ten sprzęt, co umożliwia zwrot klucza podrzędnego do oprogramowania na potrzeby zadań, które nie mogą korzystać z wbudowanego silnika kryptograficznego.

Hierarchia kluczy

Klucze można wyprowadzić z innych kluczy za pomocą funkcji derywacji klucza (KDF), takiej jak HKDF, co powoduje powstanie hierarchii kluczy.

Na poniższym diagramie przedstawiono typową hierarchię kluczy w przypadku FBE, gdy nie używa się kluczy spakowanych w sprzęcie:

Hierarchia kluczy FBE (standardowa)
Rysunek 1. Hierarchia kluczy FBE (standardowa)

Klucz klasy FBE to surowy klucz szyfrowania, który Android przekazuje jądrowi systemu Linux, aby odblokować określony zestaw zaszyfrowanych katalogów, np. pamięć szyfrowana hasłem dla konkretnego użytkownika Androida. W jądrze ten klucz jest nazywany kluczem głównym fscrypt. Z tego klucza jądro uzyskuje te podklucze:

  • Identyfikator klucza. Nie jest ona używana do szyfrowania, lecz używana do identyfikowania klucza, za pomocą którego chroniony jest określony plik lub katalog.
  • Klucz szyfrowania zawartości pliku
  • Klucz szyfrowania nazw plików

Natomiast na poniższym diagramie przedstawiono hierarchię kluczy w przypadku szyfrowania po stronie klienta z użyciem kluczy szyfrujących sprzętowych:

Hierarchia kluczy FBE (z kluczem zaszyfrowanym sprzętowo)
Rysunek 2. Hierarchia kluczy FBE (z kluczem opakowanym sprzętowo)

W porównaniu z poprzednim przykładem do hierarchii kluczy dodano dodatkowy poziom, a klucz szyfrowania zawartości pliku został przeniesiony. Węzeł główny nadal reprezentuje klucz, który Android przekazuje do Linuksa, aby odblokować zestaw zaszyfrowanych katalogów. Klucz jest jednak w tym przypadku w kształcie tymczasowym i aby go użyć, musi zostać przekazany do dedykowanego sprzętu. Sprzęt musi mieć 2 interfejsy, które przyjmują klucz zapakowany na bieżąco:

  • Jeden interfejs do wyprowadzania inline_encryption_key i bezpośredniego programowania w kluczu slotu wbudowanego silnika szyfrowania. Umożliwia to szyfrowanie/odszyfrowywanie zawartości plików bez dostępu do nieprzetworzonego klucza przez oprogramowanie. W popularnych jądrach Androida ten interfejs odpowiada operacji blk_crypto_ll_ops::keyslot_program, która musi zostać zaimplementowana przez sterownik pamięci.
  • Jeden interfejs do wyprowadzenia i zwrotu sw_secret („tajemnica oprogramowania” – w niektórych miejscach nazywana też „tajemnicą surową”), który jest kluczem używanym przez Linuksa do wyprowadzenia kluczy podrzędnych do wszystkiego oprócz szyfrowania zawartości pliku. W popularnych jądrach Androida ten interfejs odpowiada operacji blk_crypto_ll_ops::derive_sw_secret, która musi zostać zaimplementowana przez sterownik pamięci.

Aby wyprowadzić inline_encryption_keysw_secret z klucza magazynowania w postaci surowych danych, sprzęt musi używać silnego generatora kluczy KDF. Ten algorytm KDF musi być zgodny ze sprawdzonymi metodami kryptografii. Jego moc bezpieczeństwa musi wynosić co najmniej 256 bitów, co wystarcza dla każdego algorytmu używanego później. Podczas wyprowadzania każdego typu klucza podrzędnego należy też używać osobnej etykiety, kontekstu i ciągu informacji specyficznych dla aplikacji, aby zagwarantować, że otrzymane klucze podrzędne są od siebie odseparowane pod względem kryptograficznym, czyli że znajomość jednego z nich nie ujawnia informacji o żadnym innym. Rozciąganie klucza nie jest wymagane, ponieważ surowy klucz pamięci jest już kluczem losowym o jednolitej rozkładzie.

Technicznie rzecz biorąc, można użyć dowolnego KDF, który spełnia wymagania dotyczące bezpieczeństwa. Jednak na potrzeby testów należy ponownie zaimplementować ten sam plik KDF w kodzie testowym. Obecnie sprawdzona i wdrożona 1 funkcja KDF znajduje się w kodzie źródłowym vts_kernel_encryption_test. Zalecamy, aby sprzęt używał tego KDF, który korzysta z NIST SP 800-108 „KDF w trybie przeciwdziałania”AES-256-CMAC jako PRF. Pamiętaj, że aby zapewnić zgodność, wszystkie części algorytmu muszą być identyczne, w tym wybór kontekstów KDF i etykietek dla każdego klucza podrzędnego.

Zawijanie kluczy

Aby spełnić wymagania dotyczące zabezpieczeń kluczy spakowanych w sprzęt, zdefiniowano 2 typy pakowania kluczy:

  • Efemerydyczny owijacz: sprzęt szyfruje klucz pierwotny za pomocą klucza, który jest generowany losowo przy każdym uruchomieniu i nie jest bezpośrednio dostępny poza sprzętem.
  • Pakowanie długoterminowe: sprzęt szyfruje klucz w postaci danych nieprzetworzonych za pomocą unikalnego, trwałego klucza wbudowanego w sprzęcie, który nie jest bezpośrednio dostępny poza sprzętem.

Wszystkie klucze przekazywane do jądra Linuksa w celu odblokowania pamięci są tymczasowo spakowane. Dzięki temu, jeśli atakujący wydobędzie klucz używany z pamięci systemowej, nie będzie mógł go użyć nie tylko na wyłączonym urządzeniu, ale także po jego ponownym uruchomieniu.

Jednocześnie Android musi mieć możliwość przechowywania zaszyfrowanej wersji kluczy na dysku, aby można było je odblokować. Do tego celu nadają się klucze w postaci tekstu zwykłego. Zaleca się jednak, aby klucze w postaci zwykłego tekstu nigdy nie były obecne w pamięci systemowej, aby nie można było ich wyodrębnić i użyć poza urządzeniem, nawet jeśli wyodrębnienie nastąpiło w momencie uruchamiania. Z tego powodu zdefiniowano pojęcie długoterminowego opakowania.

Aby obsługiwać zarządzanie kluczami w tych 2 sposobach, sprzęt musi implementować te interfejsy:

  • Interfejsy do generowania i importowania kluczy pamięci masowej, które zwracają je w długoterminowej zapakowanej postaci. Dostęp do tych interfejsów jest pośrednio uzyskiwany przez KeyMint i odpowiada tagowi TAG_STORAGE_KEY KeyMint. Funkcja „generuj” jest używana przez vold do generowania nowych kluczy pamięci masowej na potrzeby Androida, a funkcja „importuj” jest używana przez vts_kernel_encryption_test do importowania kluczy testowych.
  • Interfejs umożliwiający konwersję długoterminowego opakowanego klucza pamięci masowej na opakowany efemerycznie klucz pamięci masowej. Odpowiada to metodzie convertStorageKeyToEphemeral KeyMint. Ta metoda jest używana przez vold i vts_kernel_encryption_test do odblokowania pamięci.

Algorytm szyfrowania klucza to szczegół implementacji, ale powinien używać silnego algorytmu AEAD, takiego jak AES-256-GCM z losowymi wartościami IV.

Wymagane zmiany w oprogramowaniu

AOSP zawiera już podstawowy framework do obsługi kluczy zaszyfrowanych sprzętowo. Obejmuje to obsługę w komponentach przestrzeni użytkownika, takich jak vold, a także obsługę jądra Linuxa w blk-crypto, fscryptdm-default-key.

Wymagane są jednak pewne zmiany związane z wdrożeniem.

Zmiany w KeyMint

Implementacja KeyMint na urządzeniu musi zostać zmodyfikowana, aby obsługiwać metodę TAG_STORAGE_KEY i implementować metodę convertStorageKeyToEphemeral.

W Keymaster zamiast convertStorageKeyToEphemeral użyto exportKey.

Zmiany w jądrze Linuksa

Sterownik jądra Linuxa dla wbudowanego modułu szyfrowania urządzenia musi zostać zmodyfikowany, aby obsługiwał klucze szyfrowane sprzętowo.

W przypadku jąder android14 i wyższych: ustaw BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDblk_crypto_profile::key_types_supported, wykonaj blk_crypto_ll_ops::keyslot_programblk_crypto_ll_ops::keyslot_evict obsługuj programowanie i usuwanie kluczy w ramach szyfrowania sprzętowego i wdróż blk_crypto_ll_ops::derive_sw_secret.

W przypadku jąder android12android13: ustaw BLK_CRYPTO_FEATURE_WRAPPED_KEYSblk_keyslot_manager::features, upewnij się, że blk_ksm_ll_ops::keyslot_programblk_ksm_ll_ops::keyslot_evict obsługują programowanie i wyrzucanie kluczy sprzętowych oraz zaimplementuj blk_ksm_ll_ops::derive_raw_secret.

W przypadku jąder android11 ustaw BLK_CRYPTO_FEATURE_WRAPPED_KEYSkeyslot_manager::features, wykonaj keyslot_mgmt_ll_ops::keyslot_programkeyslot_mgmt_ll_ops::keyslot_evict, obsłuż programowanie i wyrzucanie kluczy w ramach sprzętowych oraz wdrożenie keyslot_mgmt_ll_ops::derive_raw_secret.

Testowanie

Chociaż szyfrowanie za pomocą kluczy sprzętowych jest trudniejsze do przetestowania niż szyfrowanie za pomocą standardowych kluczy, można je przetestować, importując klucz testowy i ponownie implementując wyprowadzenie klucza, które wykonuje sprzęt. Jest to implementowane w vts_kernel_encryption_test. Aby przeprowadzić ten test:

atest -v vts_kernel_encryption_test

Przeczytaj dziennik testu i sprawdź, czy przypadki testowe kluczy zaszyfrowanych sprzętowo (na przykład FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicyDmDefaultKeyTest.TestHwWrappedKey) nie zostały pominięte z powodu braku obsługi kluczy zaszyfrowanych sprzętowo, ponieważ w takim przypadku wyniki testu są nadal „pozytywne”.

Włączanie klawiszy

Gdy obsługa kluczy sprzętowych zapakowanych w sprzęcie na urządzeniu będzie działać prawidłowo, możesz wprowadzić w pliku fstab urządzenia te zmiany, aby Android używał go do szyfrowania metadanych i szyfrowania pełnego dysku:

  • FBE: dodaj flagę wrappedkey_v0 do parametru fileencryption. Użyj na przykład elementu fileencryption=::inlinecrypt_optimized+wrappedkey_v0. Więcej informacji znajdziesz w dokumentacji FBE.
  • Szyfrowanie metadanych: dodaj flagę wrappedkey_v0 do parametru metadata_encryption. Użyj na przykład elementu metadata_encryption=:wrappedkey_v0. Więcej informacji znajdziesz w dokumentacji dotyczącej szyfrowania metadanych.