硬體包裝金鑰

與大多數磁碟和檔案加密軟體一樣,Android 的儲存空間加密機制傳統上會依賴系統記憶體中的原始加密金鑰,以便執行加密作業。即使加密作業是由專用硬體而非軟體執行,軟體通常仍須管理原始加密金鑰。

傳統上,這類情況不會被視為問題,因為離線攻擊時不會出現金鑰,而這正是儲存空間加密功能主要防範的攻擊類型。不過,我們希望提供更完善的保護,防範其他類型的攻擊,例如冷啟動攻擊和線上攻擊。在這些攻擊中,攻擊者可能不需要完全入侵裝置,就能洩漏系統記憶體。

為解決這個問題,Android 11 導入了硬體包裝金鑰支援功能 (適用於支援硬體的裝置)。硬體包裝金鑰是儲存金鑰,只有專用硬體知道其原始形式;軟體只會看到並使用這些金鑰的包裝 (加密) 形式。這項硬體必須能夠產生及匯入儲存空間金鑰、以暫時性和長期形式包裝儲存空間金鑰、衍生子金鑰、直接將一個子金鑰程式設計到內嵌加密引擎,以及將個別子金鑰傳回軟體。

注意:內嵌加密引擎 (或內嵌加密硬體) 是指在資料傳輸至/自儲存裝置時,對資料進行加密/解密的硬體。通常這是 UFS 或 eMMC 主機控制器,可實作相應 JEDEC 規格定義的加密擴充功能。

設計

本節介紹硬體包裝金鑰功能的設計,包括需要哪些硬體支援。本討論內容著重於檔案型加密 (FBE),但解決方案也適用於中繼資料加密

如要避免在系統記憶體中保留原始加密金鑰,其中一種做法是只將金鑰保留在內嵌加密引擎的金鑰插槽中。不過,這種做法會遇到一些問題:

  • 加密金鑰數量可能超出金鑰插槽數量。
  • 如果儲存空間主機控制器重設,內嵌加密引擎通常會遺失金鑰插槽的內容。重設儲存空間主機控制器是標準的錯誤復原程序,會在發生特定類型的儲存空間錯誤時執行,這類錯誤隨時可能發生。因此,使用內嵌加密時,作業系統必須隨時準備好重新編程金鑰插槽,不需使用者介入。
  • 內嵌加密引擎只能用於加密/解密磁碟上的完整資料區塊。不過,如果是 FBE,軟體仍需執行其他加密編譯工作,例如加密檔案名稱和衍生金鑰 ID。軟體仍需存取原始 FBE 金鑰,才能執行這項其他工作。

為避免發生這些問題,儲存空間金鑰會改為硬體包裝金鑰,只能由專用硬體解包並使用。這樣一來,系統就能支援無限數量的金鑰。此外,金鑰階層會經過修改,並部分移至這個硬體,讓子金鑰可傳回軟體,以執行無法使用內嵌加密引擎的工作。

金鑰階層

金鑰可使用金鑰衍生函式 (KDF) (例如 HKDF) 從其他金鑰衍生而來,進而形成金鑰階層

下圖顯示 FBE 的典型金鑰階層,其中使用硬體包裝金鑰:

FBE 金鑰階層 (標準)
圖 1:FBE 金鑰階層 (標準)

FBE 類別金鑰是原始加密金鑰,Android 會將其傳遞至 Linux 核心,以解鎖特定加密目錄集,例如特定 Android 使用者的憑證加密儲存空間。(在核心中,這個金鑰稱為「fscrypt 主金鑰」)。核心會從這個金鑰衍生下列子金鑰:

  • 金鑰 ID。這並非用於加密,而是用來識別保護特定檔案或目錄的金鑰。
  • 檔案內容加密金鑰
  • 檔案名稱加密金鑰

相較之下,下圖顯示使用硬體包裝金鑰時,FBE 的金鑰階層:

FBE 金鑰階層 (含硬體包裝金鑰)
圖 2 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_keysw_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 方法。voldvts_kernel_encryption_test 都會使用這個方法解鎖儲存空間。

金鑰包裝演算法是實作詳細資料,但應使用強大的 AEAD,例如 AES-256-GCM,並搭配隨機 IV。

必須變更軟體

AOSP 已有支援硬體包裝金鑰的基本架構。這包括使用者空間元件 (例如 vold) 的支援,以及 Linux 核心的 blk-cryptofscryptdm-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_programblk_crypto_ll_ops::keyslot_evict,支援程式設計/清除硬體包裝金鑰,並實作 blk_crypto_ll_ops::derive_sw_secret

針對 android12android13 核心,請在 blk_keyslot_manager::features 中設定 BLK_CRYPTO_FEATURE_WRAPPED_KEYS,製作 blk_ksm_ll_ops::keyslot_programblk_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_programkeyslot_mgmt_ll_ops::keyslot_evict,支援程式設計/逐出硬體包裝的金鑰,並實作 keyslot_mgmt_ll_ops::derive_raw_secret

測試包裝金鑰

雖然使用硬體包裝金鑰加密比使用原始金鑰加密更難測試,但您還是可以匯入測試金鑰,並重新實作硬體執行的金鑰衍生作業,藉此進行測試。這項功能是在 vts_kernel_encryption_test 中實作。如要執行這項測試,請執行:

atest -v vts_kernel_encryption_test

請閱讀測試記錄,並確認硬體包裝金鑰測試案例 (例如 FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicyDmDefaultKeyTest.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。詳情請參閱中繼資料加密說明文件