Klucze obudowane sprzętowo

Podobnie jak większość oprogramowania do szyfrowania dysków i plików, szyfrowanie miejsca na dane w Androidzie tradycyjnie opiera się na tym, że surowe klucze szyfrowania znajdują się w pamięci systemowej, dzięki czemu można przeprowadzić szyfrowanie. Nawet jeśli szyfrowanie jest wykonywane przez dedykowany sprzęt, a nie oprogramowanie, oprogramowanie nadal musi zarządzać surowymi kluczami szyfrowania.

Tradycyjnie nie jest to postrzegane jako problem, ponieważ klucze nie są obecne podczas ataku offline, który jest głównym typem ataku, przed którym ma chronić szyfrowanie miejsca na dane. Istnieje jednak potrzeba zwiększenia ochrony przed innymi rodzajami ataków, takimi jak ataki typu cold boot i ataki online, w których atakujący może wyciec pamięć systemową bez pełnego przejęcia kontroli nad urządzeniem.

Aby rozwiązać ten problem, w Androidzie 11 wprowadzono obsługę kluczy sprzętowych, jeśli jest dostępna obsługa sprzętowa. Klucze sprzętowe to klucze miejsca na dane, które są znane w postaci surowej tylko dedykowanemu sprzętowi. Oprogramowanie widzi te klucze i działa z nimi tylko w postaci opakowanej (zaszyfrowanej). Ten sprzęt musi być w stanie generować i importować klucze miejsca na dane, opakowywać klucze miejsca na dane w postaci efemerycznej i długoterminowej, wyprowadzać klucze podrzędne, bezpośrednio programować jeden klucz podrzędny w wbudowanym silniku kryptograficznym i zwracać oddzielny klucz podrzędny do oprogramowania.

Uwaga: Wbudowany silnik kryptograficzny (lub wbudowany sprzęt do szyfrowania) to sprzęt, który szyfruje i odszyfrowuje dane podczas ich przesyłania do i z urządzenia pamięci masowej. Zwykle jest to kontroler hosta UFS lub eMMC, który implementuje rozszerzenia kryptograficzne zdefiniowane w odpowiedniej specyfikacji JEDEC.

Projektowanie

W tej sekcji przedstawiamy projekt funkcji kluczy sprzętowych, w tym wymagania dotyczące obsługi sprzętowej. Ta dyskusja koncentruje się na szyfrowaniu opartym na plikach (FBE), ale rozwiązanie dotyczy też szyfrowania metadanych.

Jednym ze sposobów na uniknięcie konieczności przechowywania surowych kluczy szyfrowania w pamięci systemowej byłoby przechowywanie ich tylko w gniazdach kluczy wbudowanego silnika kryptograficznego. To podejście ma jednak pewne problemy:

  • Liczba kluczy szyfrowania może przekraczać liczbę gniazd kluczy.
  • Wbudowane silniki kryptograficzne zwykle tracą zawartość swoich gniazd kluczy, jeśli zresetowany zostanie kontroler hosta pamięci masowej. Resetowanie kontrolera hosta pamięci masowej to a standardowa procedura odzyskiwania po błędach, która jest wykonywana w przypadku wystąpienia niektórych rodzajów błędów pamięci masowej . Takie błędy mogą wystąpić w dowolnym momencie. Dlatego, gdy używane jest wbudowane szyfrowanie, system operacyjny musi być zawsze gotowy do przeprogramowania gniazd kluczy bez interwencji użytkownika.
  • Wbudowane silniki kryptograficzne mogą być używane tylko do szyfrowania i odszyfrowywania pełnych bloków danych na dysku. W przypadku FBE oprogramowanie nadal musi być w stanie wykonywać inne operacje kryptograficzne, takie jak szyfrowanie nazw plików i wyprowadzanie identyfikatorów kluczy. Aby to zrobić, oprogramowanie nadal będzie potrzebować dostępu do surowych kluczy FBE, aby wykonać tę inną pracę.

Aby uniknąć tych problemów, klucze miejsca na dane są przekształcane w klucze sprzętowe, które mogą być rozpakowywane i używane tylko przez dedykowany sprzęt. Pozwala to na obsługę nieograniczonej liczby kluczy. Ponadto modyfikowana jest hierarchia kluczy i częściowo przenoszona do tego sprzętu, co umożliwia zwracanie klucza podrzędnego do oprogramowania w przypadku zadań, które nie mogą korzystać z wbudowanego silnika kryptograficznego.

Hierarchia kluczy

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

Na diagramie poniżej przedstawiono typową hierarchię kluczy dla FBE, gdy nie są używane klucze sprzętowe:

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

Klucz klasy FBE to surowy klucz szyfrowania, który Android przekazuje do jądra Linuksa, aby odblokować określony zestaw zaszyfrowanych katalogów, np. miejsce na dane zaszyfrowane za pomocą danych logowania konkretnego użytkownika Androida. (W jądrze ten klucz nazywa się kluczem głównym fscrypt). Z tego klucza jądro wyprowadza te klucze podrzędne:

  • Identyfikator klucza. Nie jest on używany do szyfrowania, ale jest wartością służącą do identyfikowania klucza, za pomocą którego chroniony jest konkretny plik lub katalog.
  • Klucz szyfrowania zawartości pliku
  • Klucz szyfrowania nazw plików

Natomiast na diagramie poniżej przedstawiono hierarchię kluczy dla FBE, gdy używane są klucze sprzętowe:

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

W porównaniu z poprzednim przypadkiem 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. Teraz jednak ten klucz jest w postaci opakowanej efemerycznie i aby można go było użyć, musi zostać przekazany do dedykowanego sprzętu. Ten sprzęt musi implementować 2 interfejsy, które przyjmują klucz opakowany efemerycznie:

  • Interfejs do wyprowadzania inline_encryption_key i bezpośredniego programowania go w gnieździe kluczy wbudowanego silnika kryptograficznego. Umożliwia to szyfrowanie i odszyfrowywanie zawartości plików bez dostępu oprogramowania do surowego klucza. W typowych jądrach Androida ten interfejs odpowiada operacji blk_crypto_ll_ops::keyslot_program , która musi być zaimplementowana przez sterownik pamięci masowej.
  • Interfejs do wyprowadzania i zwracania sw_secret („tajemnicy oprogramowania” – wcześniej nazywanej „surową tajemnicą”), czyli klucza, którego Linux używa do wyprowadzania kluczy podrzędnych do wszystkiego poza szyfrowaniem zawartości plików. W typowych jądrach Androida ten interfejs odpowiada operacji blk_crypto_ll_ops::derive_sw_secret , która musi być zaimplementowana przez sterownik pamięci masowej.

Aby wyprowadzić inline_encryption_key i sw_secret z surowego klucza miejsca na dane, sprzęt musi używać silnej kryptograficznie funkcji KDF. Ta funkcja KDF musi być zgodna z najlepszymi praktykami kryptograficznymi. Musi mieć siłę zabezpieczeń co najmniej 256 bitów, czyli wystarczającą dla każdego algorytmu używanego później. Musi też używać odrębnej etykiety i kontekstu podczas wyprowadzania każdego typu klucza podrzędnego, aby zagwarantować, że wynikowe klucze podrzędne są kryptograficznie odizolowane, czyli znajomość jednego z nich nie ujawnia żadnego innego. Rozciąganie klucza nie jest wymagane, ponieważ surowy klucz miejsca na dane jest już kluczem losowym.

Technicznie można użyć dowolnej funkcji KDF, która spełnia wymagania dotyczące bezpieczeństwa. Jednak na potrzeby testowania vts_kernel_encryption_test implementuje tę samą funkcję KDF w oprogramowaniu, aby odtworzyć tekst zaszyfrowany na dysku i sprawdzić, czy jest on prawidłowy. Aby ułatwić testowanie i zapewnić użycie bezpiecznej i sprawdzonej już funkcji KDF, zalecamy, aby sprzęt implementował domyślną funkcję KDF, którą sprawdza test. W przypadku sprzętu, który używa innej funkcji KDF, zobacz Testowanie kluczy opakowanych, aby dowiedzieć się, jak odpowiednio skonfigurować test.

Opakowywanie kluczy

Aby spełnić cele bezpieczeństwa kluczy sprzętowych, zdefiniowano 2 typy opakowywania kluczy:

  • Opakowywanie efemeryczne: sprzęt szyfruje surowy klucz za pomocą klucza który jest losowo generowany przy każdym uruchomieniu i nie jest bezpośrednio udostępniany poza sprzętem.
  • Opakowywanie długoterminowe: sprzęt szyfruje surowy klucz za pomocą unikalnego, trwałego klucza wbudowanego w sprzęt, który nie jest bezpośrednio udostępniany poza sprzętem.

Wszystkie klucze przekazywane do jądra Linuksa w celu odblokowania miejsca na dane są opakowane efemerycznie. Dzięki temu, jeśli atakujący zdoła wyekstrahować używany klucz z pamięci systemowej, ten klucz będzie bezużyteczny nie tylko poza urządzeniem, ale też na urządzeniu po ponownym uruchomieniu.

Jednocześnie Android nadal musi być w stanie przechowywać zaszyfrowaną wersję kluczy na dysku, aby można je było odblokować. Do tego celu można użyć surowych kluczy. Warto jednak nigdy nie przechowywać surowych kluczy w pamięci systemowej, aby nie można ich było wyekstrahować i użyć poza urządzeniem, nawet jeśli zostaną wyekstrahowane podczas uruchamiania. Z tego powodu zdefiniowano koncepcję opakowywania długoterminowego.

Aby obsługiwać zarządzanie kluczami opakowanymi na te 2 różne sposoby, sprzęt musi implementować te interfejsy:

  • Interfejsy do generowania i importowania kluczy miejsca na dane, zwracające je w postaci opakowanej długoterminowo. Interfejs generate jest używany przez vold do generowania nowych kluczy miejsca na dane do użytku przez Androida. Interfejs import jest używany przez vts_kernel_encryption_test do importowania kluczy testowych.
  • Interfejs do konwertowania klucza miejsca na dane opakowanego długoterminowo na klucz miejsca na dane opakowany efemerycznie. Ten interfejs jest używany zarówno przez vold jak i vts_kernel_encryption_test do odblokowywania miejsca na dane.

Algorytm opakowywania kluczy jest szczegółem implementacji, ale powinien używać silnego algorytmu AEAD, takiego jak AES-256-GCM, z losowymi wektorami inicjującymi.

Wymagane zmiany w oprogramowaniu

AOSP ma już podstawową strukturę do obsługi kluczy sprzętowych. Obejmuje to obsługę w komponentach przestrzeni użytkownika, takich jak vold, a także obsługę jądra Linuksa w blk-crypto, fscrypt i dm-default-key.

Zmiany w jądrze Linuksa

Sterownik jądra Linuksa dla kontrolera pamięci masowej urządzenia z obsługą wbudowanego szyfrowania musi zostać zmodyfikowany, aby obsługiwać klucze sprzętowe.

W przypadku jąder android17 i nowszych:

  • Ustaw BLK_CRYPTO_KEY_TYPE_HW_WRAPPED w blk_crypto_profile::key_types_supported.
  • Spraw, aby blk_crypto_ll_ops::keyslot_program obsługiwał programowanie kluczy sprzętowych.
  • Spraw, aby blk_crypto_ll_ops::keyslot_evict obsługiwał usuwanie kluczy sprzętowych.
  • Zaimplementuj blk_crypto_ll_ops::derive_sw_secret, blk_crypto_ll_ops::import_key, blk_crypto_ll_ops::generate_key, i blk_crypto_ll_ops::prepare_key.

W przypadku jąder android14, android15 i android16:

  • Ustaw BLK_CRYPTO_KEY_TYPE_HW_WRAPPED w blk_crypto_profile::key_types_supported.
  • Spraw, aby blk_crypto_ll_ops::keyslot_program obsługiwał programowanie kluczy sprzętowych.
  • Spraw, aby blk_crypto_ll_ops::keyslot_evict obsługiwał usuwanie kluczy sprzętowych.
  • Zaimplementuj blk_crypto_ll_ops::derive_sw_secret.

W przypadku jąder android12 i android13:

  • Ustaw BLK_CRYPTO_FEATURE_WRAPPED_KEYS w blk_keyslot_manager::features.
  • Spraw, aby blk_ksm_ll_ops::keyslot_program obsługiwał programowanie kluczy sprzętowych.
  • Spraw, aby blk_ksm_ll_ops::keyslot_evict obsługiwał usuwanie kluczy sprzętowych.
  • Zaimplementuj blk_ksm_ll_ops::derive_raw_secret.

W przypadku jąder android11:

  • Ustaw BLK_CRYPTO_FEATURE_WRAPPED_KEYS w keyslot_manager::features.
  • Spraw, aby keyslot_mgmt_ll_ops::keyslot_program obsługiwał programowanie kluczy sprzętowych.
  • Spraw, aby keyslot_mgmt_ll_ops::keyslot_evict obsługiwał usuwanie kluczy sprzętowych.
  • Zaimplementuj keyslot_mgmt_ll_ops::derive_raw_secret.

Zmiany w KeyMint (starsza wersja)

W obecnej wersji kluczy sprzętowych (wrappedkey) generowanie, importowanie i przygotowywanie kluczy sprzętowych odbywa się za pomocą ioctls jądra Linuksa BLKCRYPTOGENERATEKEY, BLKCRYPTOIMPORTKEY i BLKCRYPTOPREPAREKEY. Te ioctls odpowiadają metodom w struct blk_crypto_ll_ops. Sterownik pamięci masowej implementuje te metody i komunikuje się ze sprzętem do opakowywania kluczy, aby wykonać żądaną operację. Więcej informacji o tych ioctls znajdziesz w dokumentacji jądra Linuksa..

Te ioctls zostały dodane w Linuksie 6.16. Na urządzeniach, które nie zostały wprowadzone na rynek z rozwiązaniem opartym na ioctl, używane jest inne rozwiązanie korzystające z Android KeyMint (lub wcześniej KeyMaster). Starsze rozwiązanie (wrappedkey_v0) nie jest zgodne z głównym jądrem Linuksa ani z obecnym rozwiązaniem. Starsze rozwiązanie korzysta z tych funkcji KeyMint:

  • Obsługa TAG_STORAGE_KEY zarówno w przypadku generowania, jak i importowania kluczy.
  • Obsługa metody convertStorageKeyToEphemeral.

Te funkcje KeyMint są potrzebne tylko na urządzeniach, które używają starszego rozwiązania odpowiadającego wrappedkey_v0 w pliku fstab.

Urządzenia, które używają obecnego rozwiązania odpowiadającego wrappedkey w pliku fstab, nie muszą implementować tych funkcji KeyMint.

Testowanie kluczy opakowanych

Chociaż testowanie szyfrowania za pomocą kluczy sprzętowych jest trudniejsze niż testowanie szyfrowania za pomocą kluczy surowych, nadal można je przeprowadzić, importując klucz testowy i ponownie implementując wyprowadzanie kluczy, które wykonuje sprzęt. Jest to zaimplementowane w vts_kernel_encryption_test. Aby uruchomić ten test, wykonaj to polecenie:

atest -v vts_kernel_encryption_test

Przeczytaj log testu i sprawdź, czy przypadki testowe kluczy sprzętowych (np. FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy i DmDefaultKeyTest.TestHwWrappedKey) nie zostały pominięte z powodu niewykrycia obsługi kluczy sprzętowych, ponieważ w takim przypadku wyniki testu są nadal „zaliczone”.

Domyślnie vts_kernel_encryption_test zakłada, że sprzęt implementuje funkcję KDF, którą nazywa kdf1. Ta funkcja KDF należy do rodziny funkcji KDF w trybie licznika z NIST SP 800-108 i używa AES-256-CMAC jako funkcji pseudolosowej. Więcej informacji o CMAC znajdziesz w specyfikacji CMAC. Funkcja KDF używa określonych kontekstów i etykiet podczas wyprowadzania każdego klucza podrzędnego. Sprzęt powinien implementować tę funkcję KDF, w tym dokładny wybór kontekstu, etykiety i formatowania stałego ciągu wejściowego podczas wyprowadzania każdego klucza podrzędnego.

Jednak vts_kernel_encryption_test implementuje też dodatkowe funkcje KDF kdf2 do kdf4. Są one równie bezpieczne jak kdf1 i różnią się tylko wyborem kontekstów, etykiet i formatowaniem stałego ciągu wejściowego. Istnieją tylko po to, aby obsługiwać różne urządzenia.

W przypadku urządzeń, które używają innej funkcji KDF, ustaw właściwość systemową ro.crypto.hw_wrapped_keys.kdf w PRODUCT_VENDOR_PROPERTIES na nazwę funkcji KDF zdefiniowaną w kodzie źródłowym testu. Spowoduje to, że vts_kernel_encryption_test będzie sprawdzać tę funkcję KDF zamiast kdf1. Aby na przykład wybrać kdf2, użyj tego polecenia:

PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2

W przypadku urządzeń, które używają funkcji KDF nieobsługiwanej przez test, dodaj też implementację tej funkcji KDF do testu i nadaj jej unikalną nazwę.

Włączanie kluczy opakowanych

Gdy obsługa kluczy sprzętowych urządzenia działa prawidłowo, wprowadź te zmiany w pliku fstab urządzenia, aby Android używał jej do szyfrowania FBE i metadanych:

  • FBE: dodaj flagę wrappedkey (lub wrappedkey_v0 w przypadku starszej wersji) do parametru fileencryption. Użyj na przykład fileencryption=::inlinecrypt_optimized+wrappedkey. Więcej informacji znajdziesz w dokumentacji FBE.
  • Szyfrowanie metadanych: dodaj flagę wrappedkey (lub wrappedkey_v0 w przypadku starszej wersji) do metadata_encryption parametru. Użyj na przykład metadata_encryption=:wrappedkey. Więcej informacji znajdziesz w dokumentacji szyfrowania metadanych.

W każdym przypadku dostępne są 2 wersje flagi:

  • wrappedkey, obsługiwana przez Androida 17 i nowsze wersje, włącza obecną wersję kluczy sprzętowych. Ta wersja jest zgodna z głównym jądrem Linuksa.
  • wrappedkey_v0, obsługiwana przez Androida 11 i nowsze wersje, włącza starszą wersję kluczy sprzętowych. Ta wersja nie jest zgodna z głównym jądrem Linuksa. Przekazuje niektóre operacje przez KeyMint i używa niestandardowego formatu na dysku. Więcej informacji znajdziesz w sekcji Zmiany w KeyMint (starsza wersja).

Na urządzeniach wprowadzanych na rynek z Androidem 17 lub nowszym preferuj wrappedkey.

Na urządzeniach, które zostały już wprowadzone na rynek z wrappedkey_v0, nadal używaj wrappedkey_v0 ze względu na zgodność wsteczną.