與大多數磁碟和檔案加密軟體一樣,Android 的儲存空間加密機制傳統上會依賴系統記憶體中的原始加密金鑰,以便執行加密作業。即使加密作業是由專用硬體而非軟體執行,軟體通常仍須管理原始加密金鑰。
傳統上,這類情況不會被視為問題,因為離線攻擊時不會出現金鑰,而這正是儲存空間加密功能主要防範的攻擊類型。不過,我們希望提供更完善的保護,防範其他類型的攻擊,例如冷啟動攻擊和線上攻擊。在這些攻擊中,攻擊者可能不需要完全入侵裝置,就能洩漏系統記憶體。
為解決這個問題,Android 11 導入了硬體包裝金鑰支援功能 (適用於支援硬體的裝置)。硬體包裝金鑰是儲存金鑰,只有專用硬體知道其原始形式;軟體只會看到並使用這些金鑰的包裝 (加密) 形式。這項硬體必須能夠產生及匯入儲存空間金鑰、以暫時性和長期形式包裝儲存空間金鑰、衍生子金鑰、直接將一個子金鑰程式設計到內嵌加密引擎,以及將個別子金鑰傳回軟體。
注意:內嵌加密引擎 (或內嵌加密硬體) 是指在資料傳輸至/自儲存裝置時,對資料進行加密/解密的硬體。通常這是 UFS 或 eMMC 主機控制器,可實作相應 JEDEC 規格定義的加密擴充功能。
設計
本節介紹硬體包裝金鑰功能的設計,包括需要哪些硬體支援。本討論內容著重於檔案型加密 (FBE),但解決方案也適用於中繼資料加密。
如要避免在系統記憶體中保留原始加密金鑰,其中一種做法是只將金鑰保留在內嵌加密引擎的金鑰插槽中。不過,這種做法會遇到一些問題:
- 加密金鑰數量可能超出金鑰插槽數量。
- 如果儲存空間主機控制器重設,內嵌加密引擎通常會遺失金鑰插槽的內容。重設儲存空間主機控制器是標準的錯誤復原程序,會在發生特定類型的儲存空間錯誤時執行,這類錯誤隨時可能發生。因此,使用內嵌加密時,作業系統必須隨時準備好重新編程金鑰插槽,不需使用者介入。
- 內嵌加密引擎只能用於加密/解密磁碟上的完整資料區塊。不過,如果是 FBE,軟體仍需執行其他加密編譯工作,例如加密檔案名稱和衍生金鑰 ID。軟體仍需存取原始 FBE 金鑰,才能執行這項其他工作。
為避免發生這些問題,儲存空間金鑰會改為硬體包裝金鑰,只能由專用硬體解包並使用。這樣一來,系統就能支援無限數量的金鑰。此外,金鑰階層會經過修改,並部分移至這個硬體,讓子金鑰可傳回軟體,以執行無法使用內嵌加密引擎的工作。
金鑰階層
金鑰可使用金鑰衍生函式 (KDF) (例如 HKDF) 從其他金鑰衍生而來,進而形成金鑰階層。
下圖顯示 FBE 的典型金鑰階層,其中未使用硬體包裝金鑰:
FBE 類別金鑰是原始加密金鑰,Android 會將其傳遞至 Linux 核心,以解鎖特定加密目錄集,例如特定 Android 使用者的憑證加密儲存空間。(在核心中,這個金鑰稱為「fscrypt 主金鑰」)。核心會從這個金鑰衍生下列子金鑰:
- 金鑰 ID。這並非用於加密,而是用來識別保護特定檔案或目錄的金鑰。
- 檔案內容加密金鑰
- 檔案名稱加密金鑰
相較之下,下圖顯示使用硬體包裝金鑰時,FBE 的金鑰階層:
與先前案例相比,金鑰階層中新增了一個層級,檔案內容加密金鑰也已重新放置。根節點仍代表 Android 傳遞給 Linux 的金鑰,用於解鎖一組加密目錄。不過,該金鑰現在是以臨時包裝的形式存在,必須傳遞至專用硬體才能使用。這項硬體必須實作兩個介面,並採用臨時包裝的金鑰:
- 透過單一介面衍生
inline_encryption_key
,並直接將其程式設計到內嵌加密引擎的鍵槽中。這樣一來,軟體不必存取原始金鑰,就能加密/解密檔案內容。在 Android 通用核心中,這個介面會對應至blk_crypto_ll_ops::keyslot_program
作業,且必須由儲存空間驅動程式實作。 - 這個介面可衍生及傳回
sw_secret
(「軟體密鑰」,在某些地方也稱為「原始密鑰」),這是 Linux 用來衍生所有子金鑰的金鑰,但檔案內容加密除外。在 Android 通用核心中,這個介面會對應至blk_crypto_ll_ops::derive_sw_secret
作業,且必須由儲存空間驅動程式實作。
如要從原始儲存金鑰衍生 inline_encryption_key
和 sw_secret
,硬體必須使用加密編譯強度高的 KDF。這個 KDF 必須遵循密碼編譯最佳做法,且安全強度至少須達 256 位元,也就是足以用於後續使用的任何演算法。此外,在衍生每種子金鑰時,也必須使用不同的標籤和環境,確保產生的子金鑰在密碼學上相互獨立,也就是說,瞭解其中一個子金鑰不會揭露任何其他子金鑰。原始儲存空間金鑰已是隨機金鑰,因此不需要金鑰延展。
從技術上來說,只要符合安全性規定,任何 KDF 都可以使用。
不過,為了進行測試,vts_kernel_encryption_test
會在軟體中實作相同的 KDF,以重現磁碟上的密文並驗證是否正確。為方便測試並確保使用安全且已審查的 KDF,建議硬體實作測試檢查的預設 KDF。如要瞭解如何為使用不同 KDF 的硬體設定測試,請參閱「測試包裝金鑰」。
金鑰包裝
為達到硬體包裝金鑰的安全目標,系統定義了兩種金鑰包裝類型:
- 暫時性包裝:硬體會使用每次啟動時隨機產生的金鑰加密原始金鑰,且不會直接在硬體外部公開。
- 長期包裝:硬體會使用內建的永久性不重複金鑰加密原始金鑰,該金鑰不會直接向硬體外部公開。
傳遞至 Linux 核心以解鎖儲存空間的所有金鑰,都會以暫時性方式包裝。這可確保即使攻擊者能從系統記憶體中擷取使用中的金鑰,該金鑰不僅無法在裝置外使用,重新啟動後也無法在裝置上使用。
同時,Android 仍需能夠將金鑰的加密版本儲存在磁碟上,以便解鎖。原始金鑰可用於此用途。不過,最好不要讓原始金鑰出現在系統記憶體中,這樣即使在開機時擷取金鑰,也不會被用於裝置外。因此,我們定義了長期包裝的概念。
如要支援以這兩種不同方式包裝的金鑰管理作業,硬體必須實作下列介面:
- 介面可產生及匯入儲存空間金鑰,並以長期包裝形式傳回金鑰。這些介面是透過 KeyMint 間接存取,且對應至
TAG_STORAGE_KEY
KeyMint 標記。vold
會使用「產生」功能產生新的儲存空間金鑰,供 Android 使用;vts_kernel_encryption_test
則會使用「匯入」功能匯入測試金鑰。 - 這個介面可將長期包裝的儲存金鑰轉換為暫時包裝的儲存金鑰。這對應於
convertStorageKeyToEphemeral
KeyMint 方法。vold
和vts_kernel_encryption_test
都會使用這個方法解鎖儲存空間。
金鑰包裝演算法是實作詳細資料,但應使用強大的 AEAD,例如 AES-256-GCM,並搭配隨機 IV。
必須變更軟體
AOSP 已有支援硬體包裝金鑰的基本架構。這包括使用者空間元件 (例如 vold
) 的支援,以及 Linux 核心的 blk-crypto、fscrypt 和 dm-default-key 支援。
不過,您必須進行一些導入作業專屬的變更。
KeyMint 異動
必須修改裝置的 KeyMint 實作項目,才能支援 TAG_STORAGE_KEY
並實作 convertStorageKeyToEphemeral
方法。
在 Keymaster 中,使用的是 exportKey
,而非 convertStorageKeyToEphemeral
。
Linux 核心變更
必須修改裝置內建加密引擎的 Linux 核心驅動程式,才能支援硬體包裝金鑰。
如果是 android14
以上的核心,請在 blk_crypto_profile::key_types_supported
中設定 BLK_CRYPTO_KEY_TYPE_HW_WRAPPED
,製作 blk_crypto_ll_ops::keyslot_program
和 blk_crypto_ll_ops::keyslot_evict
,支援程式設計/清除硬體包裝金鑰,並實作 blk_crypto_ll_ops::derive_sw_secret
。
針對 android12
和 android13
核心,請在 blk_keyslot_manager::features
中設定 BLK_CRYPTO_FEATURE_WRAPPED_KEYS
,製作 blk_ksm_ll_ops::keyslot_program
和 blk_ksm_ll_ops::keyslot_evict
,支援程式設計/清除硬體包裝金鑰,並實作 blk_ksm_ll_ops::derive_raw_secret
。
針對 android11
核心,在 keyslot_manager::features
中設定 BLK_CRYPTO_FEATURE_WRAPPED_KEYS
,建立 keyslot_mgmt_ll_ops::keyslot_program
和 keyslot_mgmt_ll_ops::keyslot_evict
,支援程式設計/逐出硬體包裝的金鑰,並實作 keyslot_mgmt_ll_ops::derive_raw_secret
。
測試包裝金鑰
雖然使用硬體包裝金鑰加密比使用原始金鑰加密更難測試,但您還是可以匯入測試金鑰,並重新實作硬體執行的金鑰衍生作業,藉此進行測試。這項功能是在 vts_kernel_encryption_test
中實作。如要執行這項測試,請執行:
atest -v vts_kernel_encryption_test
請閱讀測試記錄,並確認硬體包裝金鑰測試案例 (例如 FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy
和 DmDefaultKeyTest.TestHwWrappedKey
) 未因未偵測到硬體包裝金鑰支援而略過,因為在這種情況下,測試結果仍為「通過」。
根據預設,vts_kernel_encryption_test
會假設硬體實作了名為 kdf1
的 KDF。這個 KDF 屬於 NIST SP 800-108 的計數器模式 KDF 系列,並使用 AES-256-CMAC 做為偽隨機函式。如要進一步瞭解 CMAC,請參閱 CMAC 規格。KDF 會在衍生每個子金鑰時使用特定內容和標籤。硬體應實作這個 KDF,包括在衍生每個子金鑰時,準確選擇固定輸入字串的內容、標籤和格式。
不過,vts_kernel_encryption_test
也會透過 kdf4
實作額外的 KDF kdf2
。這些方法與 kdf1
的安全性相同,只有固定輸入字串的環境、標籤和格式選擇不同。這些檔案僅用於因應不同的硬體。
如果裝置使用不同的 KDF,請在 PRODUCT_VENDOR_PROPERTIES
中將 ro.crypto.hw_wrapped_keys.kdf
系統屬性設為測試原始碼中定義的 KDF 名稱。這會導致 vts_kernel_encryption_test
檢查該 KDF,而不是 kdf1
。舉例來說,如要選取 kdf2
,請使用:
PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2
如果裝置使用的 KDF 不受測試支援,請將該 KDF 的實作項目新增至測試,並提供專屬名稱。
啟用包裝金鑰
如果裝置的硬體包裝金鑰支援功能運作正常,請對裝置的 fstab
檔案進行下列變更,讓 Android 將其用於 FBE 和中繼資料加密:
- FBE:將
wrappedkey_v0
旗標新增至fileencryption
參數。舉例來說,請使用fileencryption=::inlinecrypt_optimized+wrappedkey_v0
。詳情請參閱 FBE 說明文件。 - 中繼資料加密:將
wrappedkey_v0
旗標新增至metadata_encryption
參數。舉例來說,請使用metadata_encryption=:wrappedkey_v0
。詳情請參閱中繼資料加密說明文件。