Klucze obudowane sprzętowo

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

Zwykle nie jest to uważane za problem, ponieważ podczas ataku offline klucze nie są obecne, a to jest główny typ ataku, przed którym ma chronić 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, opakowywanie kluczy magazynu w formach krótko- i długoterminowych, 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: inline cryptographic engine (lub inline hardware encryption) to sprzęt, który szyfruje/odszyfrowuje dane podczas ich przesyłania do lub 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

Ta sekcja przedstawia projekt funkcji kluczy w ramach oprogramowania, w tym wymagania dotyczące sprzętu. Ta dyskusja koncentruje się na szyfrowaniu plików, ale rozwiązanie dotyczy również 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 silnika szyfrowania. Takie podejście ma jednak pewne wady:

  • Liczba kluczy szyfrowania może przekroczyć liczbę sekcji kluczy.
  • Wbudowane mechanizmy szyfrowania zazwyczaj tracą zawartość swoich kluczy, jeśli kontroler pamięci masowej (zazwyczaj 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żywane jest szyfrowanie wbudowane, system operacyjny musi być zawsze gotowy do przeprogramowania slotów kluczy bez udziału użytkownika.
  • Wbudowane mechanizmy szyfrowania można używać tylko do szyfrowania i odszyfrowywania pełnych bloków danych na dysku. W przypadku FBE oprogramowanie musi jednak nadal 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. Dzięki temu można obsługiwać nieograniczoną liczbę kluczy. Dodatkowo hierarchia kluczy jest modyfikowana i częściowo przenoszona na ten sprzęt, co pozwala na zwrócenie klucza podrzędnego do oprogramowania w przypadku zadań, które nie mogą korzystać z wbudowanego mechanizmu szyfrowania.

Hierarchia kluczy

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

Poniższy diagram przedstawia 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 szyfrujący, który Android przekazuje jądrowi systemu Linux, aby odblokować określony zestaw zaszyfrowanych katalogów, na przykład pamięć szyfrowana za pomocą poświadczeń dla konkretnego użytkownika Androida. (W jądrze ten klucz jest nazywany kluczem głównym fscrypt). Z tego klucza jądro wywodzi te klucze podrzędne:

  • Identyfikator klucza. Nie jest on używany do szyfrowania, ale jest to wartość służąca do identyfikacji klucza, którym jest chroniony dany 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 spakowanym w sprzęcie)

W porównaniu z poprzednim przykładem w hierarchii kluczy dodano dodatkowy poziom, a klucz szyfrowania zawartości pliku został przeniesiony. Węzeł główny nadal reprezentuje klucz, który Android przekazuje Linuksowi w celu odblokowania zestawu zaszyfrowanych katalogów. Klucz jest jednak w tym przypadku zaszyfrowany w tymczasowej formie i aby go użyć, musi zostać przekazany do dedykowanego sprzętu. Sprzęt musi implementować 2 interfejsy, które przyjmują klucz zapakowany w chwilowy obiekt:

  • Jeden interfejs do wyprowadzania inline_encryption_key i bezpośredniego programowania w kluczu slotu wbudowanego silnika szyfrowania. Umożliwia to szyfrowanie/odszyfrowywanie treści pliku bez dostępu oprogramowania do klucza w postaci binarnej. W ogólnych 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 wyprowadzenia i zwrotu sw_secret („tajemnicy oprogramowania” – w niektórych miejscach nazywanej też „tajemnicą surową”), która jest kluczem używanym przez Linuksa do wyprowadzenia kluczy podrzędnych do wszystkiego oprócz szyfrowania zawartości pliku. W ogólnych 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_keysw_secret z surowego klucza magazynowania, sprzęt musi używać silnego kryptograficznie 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.

Teoretycznie można użyć dowolnego klucza KDF, który spełnia wymagania dotyczące bezpieczeństwa. Jednak na potrzeby testowania należy ponownie zaimplementować ten sam plik KDF w kodzie testowym. Obecnie sprawdzono i wdrożono 1 KDF. Znajdziesz go 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.

Opakowanie klucza

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

  • Efemerydyczny owijacz: sprzęt szyfruje klucz surowy za pomocą klucza, który jest generowany losowo przy każdym uruchomieniu i nie jest bezpośrednio dostępny poza sprzętem.
  • Długoterminowe opakowanie: sprzęt szyfruje klucz pierwotny za pomocą unikalnego, trwałego klucza wbudowanego w sprzęt, 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 opakowane. 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łych danych nigdy nie były obecne w pamięci systemowej, aby nie można było ich wyodrębnić i używać 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 są zwracane w trwałej formie; Dostęp do tych interfejsów jest możliwy pośrednio przez KeyMint i odpowiadają one 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 do konwertowania długoterminowego klucza spakowanego w pamięć na klucz spakowany w pamięć krótkotrwałą. Odpowiada to metodzie convertStorageKeyToEphemeral KeyMint. Ta metoda jest używana przez vold i vts_kernel_encryption_test w celu odblokowania miejsca na dane.

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.

Konieczne są jednak pewne zmiany związane z implementacją.

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/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 log testu i sprawdź, czy przypadki testowe dotyczące kluczy zaszyfrowanych sprzętowo (np. FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicyDmDefaultKeyTest.TestHwWrappedKey) nie zostały pominięte z powodu braku obsługi kluczy zaszyfrowanych sprzętowo, ponieważ w takim przypadku wyniki testu nadal będą „przeszłone”.

Włączanie klawiszy

Gdy obsługa kluczy sprzętowych zapakowanych w 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 FBE i metadanych:

  • FBE: dodaj do parametru fileencryption flagę wrappedkey_v0. Użyj na przykład funkcji 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.