Klucze owijane sprzętowo

Podobnie jak większość oprogramowania do szyfrowania dysków i plików, szyfrowanie pamięci w systemie Android tradycyjnie opiera się na nieprzetworzonych kluczach szyfrujących znajdujących się w pamięci systemowej, dzięki czemu można przeprowadzić szyfrowanie. Nawet jeśli szyfrowanie odbywa się za pomocą dedykowanego sprzętu, a nie oprogramowania, oprogramowanie nadal musi zarządzać nieprzetworzonymi kluczami szyfrującymi.

Tradycyjnie nie jest to postrzegane jako problem, ponieważ klucze nie będą obecne podczas ataku w trybie offline, który jest głównym rodzajem ataku, przed którym ma chronić szyfrowanie magazynu. Istnieje jednak potrzeba zapewnienia zwiększonej ochrony przed innymi rodzajami ataków, takimi jak ataki typu „zimny rozruch” i ataki online, podczas których osoba atakująca może uzyskać dostęp do pamięci systemowej bez całkowitego naruszenia bezpieczeństwa urządzenia.

Aby rozwiązać ten problem, w systemie Android 11 wprowadzono obsługę kluczy opakowanych sprzętowo , w których dostępna jest obsługa sprzętu. Klucze opakowane sprzętowo to klucze pamięci, które są znane tylko w postaci surowej dla dedykowanego sprzętu; oprogramowanie widzi i współpracuje z tymi kluczami tylko w formie opakowanej (zaszyfrowanej). Sprzęt ten musi być w stanie generować i importować klucze pamięci, pakować klucze pamięci w formy efemeryczne i długoterminowe, wyprowadzać podklucze, bezpośrednio programować jeden podklucz do wbudowanego silnika kryptograficznego i zwracać oddzielny podklucz do oprogramowania.

Uwaga : wbudowany silnik kryptograficzny (lub wbudowany sprzęt szyfrujący ) odnosi się do sprzętu, który szyfruje/odszyfrowuje dane w drodze do/z urządzenia magazynującego. Zwykle jest to kontroler hosta UFS lub eMMC, który implementuje rozszerzenia kryptograficzne zdefiniowane w odpowiedniej specyfikacji JEDEC.

Projekt

W tej sekcji przedstawiono projekt funkcji kluczy opakowanych sprzętowo, w tym wymaganą obsługę sprzętową. Ta dyskusja koncentruje się na szyfrowaniu plików (FBE), ale rozwiązanie dotyczy również szyfrowania metadanych .

Jednym ze sposobów uniknięcia konieczności przechowywania nieprzetworzonych kluczy szyfrujących w pamięci systemowej byłoby trzymanie ich wyłącznie w gniazdach kluczy wbudowanego silnika kryptograficznego. Jednak takie podejście wiąże się z pewnymi problemami:

  • Liczba kluczy szyfrujących może przekraczać liczbę miejsc na klucze.
  • Wbudowanych silników kryptograficznych można używać wyłącznie do szyfrowania/odszyfrowywania pełnych bloków danych na dysku. Jednak w przypadku FBE oprogramowanie nadal musi być w stanie wykonywać inne prace kryptograficzne, takie jak szyfrowanie nazw plików i uzyskiwanie identyfikatorów kluczy. Oprogramowanie nadal będzie potrzebować dostępu do surowych kluczy FBE, aby wykonać tę inną pracę.

Aby uniknąć tych problemów, klucze pamięci są zamiast tego przekształcane w 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. Ponadto hierarchia kluczy jest modyfikowana i częściowo przenoszona na ten sprzęt, co umożliwia zwrócenie podklucza do oprogramowania w przypadku zadań, które nie mogą korzystać z wbudowanego silnika kryptograficznego.

Hierarchia kluczy

Klucze można wyprowadzić z innych kluczy za pomocą KDF (funkcji wyprowadzania klucza), takiej jak HKDF , w wyniku czego powstaje hierarchia kluczy .

Poniższy diagram przedstawia typową hierarchię kluczy dla FBE, gdy nie są używane klucze opakowane sprzętowo:

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

Klucz klasy FBE to surowy klucz szyfrowania, który Android przekazuje do jądra Linuksa w celu odblokowania określonego zestawu zaszyfrowanych katalogów, takich jak zaszyfrowany poświadczeniami magazyn dla konkretnego użytkownika Androida. (W jądrze klucz ten nazywany jest kluczem głównym fscrypt .) Z tego klucza jądro wyprowadza następujące podklucze:

  • Identyfikator klucza. Nie służy to do szyfrowania, ale raczej jest wartością używaną do identyfikacji klucza, za pomocą którego chroniony jest konkretny plik lub katalog.
  • Klucz szyfrowania zawartości pliku
  • Klucz szyfrowania nazw plików

Natomiast poniższy diagram przedstawia hierarchię kluczy dla FBE, gdy używane są klucze opakowane sprzętowo:

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

W porównaniu do poprzedniego przypadku do hierarchii kluczy dodano dodatkowy poziom oraz przeniesiono klucz szyfrowania zawartości pliku. Węzeł główny nadal reprezentuje klucz, który Android przekazuje do Linuksa w celu odblokowania zestawu zaszyfrowanych katalogów. Jednak teraz klucz ten ma postać efemerycznie opakowaną i aby można go było wykorzystać, należy przekazać go do dedykowanego sprzętu. Ten sprzęt musi implementować dwa interfejsy, które przyjmują klucz efemerycznie opakowany:

  • Jeden interfejs do wyprowadzania inline_encryption_key i bezpośredniego programowania go w szczelinie klucza wbudowanego silnika kryptograficznego. Umożliwia to szyfrowanie/odszyfrowywanie zawartości pliku bez dostępu oprogramowania do surowego klucza. W popularnych jądrach Androida ten interfejs odpowiada operacji blk_crypto_ll_ops::keyslot_program , która musi być zaimplementowana przez sterownik pamięci masowej.
  • Jeden interfejs do wyprowadzania i zwracania sw_secret („tajemnica oprogramowania” – w niektórych miejscach nazywana także „tajemnicą surową”), czyli klucza używanego przez Linuksa do wyprowadzania podkluczy do wszystkiego innego niż szyfrowanie zawartości plików. 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 masowej.

Aby wyprowadzić inline_encryption_key i sw_secret z surowego klucza pamięci, sprzęt musi używać silnego kryptograficznie KDF. Ten KDF musi przestrzegać najlepszych praktyk kryptograficznych; musi mieć siłę bezpieczeństwa co najmniej 256 bitów, tj. wystarczającą dla dowolnego algorytmu użytego później. Podczas wyprowadzania każdego typu podklucza musi także używać odrębnej etykiety, kontekstu i/lub ciągu informacji specyficznych dla aplikacji, aby zagwarantować, że powstałe podklucze są izolowane kryptograficznie, tj. znajomość jednego z nich nie ujawnia żadnego innego. Rozciąganie klucza nie jest wymagane, ponieważ surowy klucz do przechowywania jest już kluczem jednakowo losowym.

Technicznie rzecz biorąc, można zastosować dowolny KDF spełniający wymagania bezpieczeństwa. Jednak do celów testowych konieczne jest ponowne zaimplementowanie tego samego KDF w kodzie testowym. Obecnie dokonano przeglądu i wdrożenia jednego KDF; można go znaleźć w kodzie źródłowym vts_kernel_encryption_test . Zaleca się, aby sprzęt korzystał z tego KDF, który wykorzystuje NIST SP 800-108 „KDF w trybie licznika” z AES-256-CMAC jako PRF. Należy pamiętać, że aby algorytm był kompatybilny, wszystkie części algorytmu muszą być identyczne, łącznie z wyborem kontekstów KDF i etykiet dla każdego podklucza.

Opakowanie na klucze

Aby spełnić cele bezpieczeństwa kluczy opakowanych sprzętowo, zdefiniowano dwa typy zawijania kluczy:

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

Wszystkie klucze przekazywane do jądra Linuksa w celu odblokowania pamięci są efemerycznie opakowane. Gwarantuje to, że jeśli atakującemu uda się wyodrębnić używany klucz z pamięci systemowej, klucz ten będzie bezużyteczny nie tylko poza urządzeniem, ale także na urządzeniu po ponownym uruchomieniu.

Jednocześnie Android nadal musi mieć możliwość przechowywania zaszyfrowanej wersji kluczy na dysku, aby w ogóle można było je odblokować. Surowe klucze będą działać w tym celu. Jednakże pożądane jest, aby surowe klucze nigdy nie znajdowały się w pamięci systemowej, aby nigdy nie można było ich wyodrębnić w celu użycia poza urządzeniem, nawet jeśli zostaną wyodrębnione podczas uruchamiania systemu. Z tego powodu zdefiniowano pojęcie opakowania długoterminowego.

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

  • Interfejsy do generowania i importowania kluczy magazynu, zwracania ich w długoterminowej formie. Dostęp do tych interfejsów jest pośredni poprzez KeyMint i odpowiadają one znacznikowi TAG_STORAGE_KEY KeyMint. Zdolność „generuj” jest używana przez vold do generowania nowych kluczy pamięci do użytku przez Androida, podczas gdy zdolność „importowania” jest używana przez vts_kernel_encryption_test do importowania kluczy testowych.
  • Interfejs umożliwiający konwersję klucza magazynu opakowanego długoterminowo na klucz magazynu opakowanego efemerycznie. Odpowiada to metodzie convertStorageKeyToEphemeral KeyMint. Ta metoda jest używana zarówno przez vold , jak i vts_kernel_encryption_test w celu odblokowania pamięci.

Algorytm zawijania kluczy jest szczegółem implementacji, ale powinien używać silnego AEAD, takiego jak AES-256-GCM z losowymi IV.

Wymagane zmiany oprogramowania

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

Wymagane są jednak pewne zmiany specyficzne dla implementacji.

Zmiany w KeyMint

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

W Keymaster użyto exportKey zamiast convertStorageKeyToEphemeral .

Zmiany w jądrze Linuksa

Sterownik jądra systemu Linux dla wbudowanego silnika kryptograficznego urządzenia musi zostać zmodyfikowany, aby obsługiwał klucze opakowane sprzętowo.

W przypadku jąder android14 i nowszych ustaw BLK_CRYPTO_KEY_TYPE_HW_WRAPPED w blk_crypto_profile::key_types_supported , wykonaj blk_crypto_ll_ops::keyslot_program i blk_crypto_ll_ops::keyslot_evict obsługują programowanie/eksmitowanie kluczy opakowanych sprzętowo i 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 , wykonaj blk_ksm_ll_ops::keyslot_program i blk_ksm_ll_ops::keyslot_evict obsługują programowanie/eksmitowanie kluczy opakowanych sprzętowo i zaimplementuj blk_ksm_ll_ops::derive_raw_secret .

W przypadku jąder android11 ustaw BLK_CRYPTO_FEATURE_WRAPPED_KEYS w keyslot_manager::features , utwórz keyslot_mgmt_ll_ops::keyslot_program i keyslot_mgmt_ll_ops::keyslot_evict obsługują programowanie/eksmitowanie kluczy opakowanych sprzętowo i zaimplementuj keyslot_mgmt_ll_ops::derive_raw_secret .

Testowanie

Chociaż szyfrowanie za pomocą kluczy opakowanych sprzętowo jest trudniejsze do przetestowania niż szyfrowanie za pomocą kluczy standardowych, nadal można to przetestować, importując klucz testowy i ponownie implementując wyprowadzanie klucza w sposób wykonywany sprzętowo. Jest to zaimplementowane w vts_kernel_encryption_test . Aby uruchomić ten test, uruchom:

atest -v vts_kernel_encryption_test

Przeczytaj dziennik testów i sprawdź, czy przypadki testowe kluczy opakowanych sprzętowo (np. FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy i DmDefaultKeyTest.TestHwWrappedKey ) nie zostały pominięte ze względu na niewykrycie obsługi kluczy opakowanych sprzętowo, ponieważ wyniki testów będą nadal „przekazywane” w ta walizka.

Włączanie

Gdy obsługa kluczy sprzętowych urządzenia będzie działać prawidłowo, możesz wprowadzić następujące zmiany w pliku fstab urządzenia, aby system Android mógł go używać do szyfrowania FBE i metadanych:

  • FBE: dodaj flagę wrappedkey_v0 do parametru fileencryption . Na przykład użyj fileencryption=::inlinecrypt_optimized+wrappedkey_v0 . Więcej szczegółów znajdziesz w dokumentacji FBE .
  • Szyfrowanie metadanych: dodaj flagę wrappedkey_v0 do parametru metadata_encryption . Na przykład użyj metadata_encryption=:wrappedkey_v0 . Więcej szczegółów znajdziesz w dokumentacji dotyczącej szyfrowania metadanych .