硬件封裝的密鑰

與大多數磁盤和文件加密軟件一樣,Android 的存儲加密傳統上依賴於系統內存中存在的原始加密密鑰,以便可以執行加密。即使由專用硬件而不是軟件執行加密,軟件通常仍需要管理原始加密密鑰。

傳統上這不被視為問題,因為在離線攻擊期間密鑰不會出現,這是存儲加密旨在防止的主要攻擊類型。但是,希望針對其他類型的攻擊提供增強的保護,例如冷啟動攻擊和在線攻擊,其中攻擊者可能能夠在不完全損害設備的情況下泄漏系統內存。

為了解決這個問題,Android 11 引入了對硬件包裝密鑰的支持,其中存在硬件支持。硬件封裝的密鑰是存儲密鑰,專用硬件僅以原始形式知道;軟件只能以封裝(加密)形式查看和使用這些密鑰。該硬件必須能夠生成和導入存儲密鑰,以臨時和長期形式包裝存儲密鑰,派生子密鑰,將一個子密鑰直接編程到內聯加密引擎中,並將單獨的子密鑰返回給軟件。

注意內聯加密引擎(或內聯加密硬件)是指在數據進出存儲設備的途中加密/解密數據的硬件。通常這是實現相應 JEDEC 規範定義的加密擴展的 UFS 或 eMMC 主機控制器。

設計

本節介紹硬件封裝密鑰功能的設計,包括它需要哪些硬件支持。此討論側重於基於文件的加密(FBE),但該解決方案也適用於元數據加密

避免在系統內存中需要原始加密密鑰的一種方法是將它們僅保留在內聯加密引擎的密鑰槽中。但是,這種方法遇到了一些問題:

  • 加密密鑰的數量可能超過密鑰槽的數量。
  • 內聯加密引擎只能用於加密/解密磁盤上的完整數據塊。但是,在 FBE 的情況下,軟件仍然需要能夠執行其他加密工作,例如文件名加密和派生密鑰標識符。軟件仍然需要訪問原始 FBE 密鑰才能完成其他工作。

為了避免這些問題,存儲密鑰改為硬件包裝密鑰,只能由專用硬件解包和使用。這允許支持無限數量的鍵。此外,密鑰層次結構被修改並部分移動到此硬件,這允許將子密鑰返回給軟件以用於無法使用內聯加密引擎的任務。

密鑰層次結構

可以使用諸如HKDFKDF(密鑰派生函數)從其他密鑰派生密鑰,從而形成密鑰層次結構

下圖描述了使用硬件封裝密鑰時 FBE 的典型密鑰層次結構:

FBE 密鑰層次結構(標準)
圖 1. FBE 密鑰層次結構(標準)

FBE 類密鑰是 Android 傳遞給 Linux 內核以解鎖一組特定的加密目錄的原始加密密鑰,例如特定 Android 用戶的憑據加密存儲。 (在內核中,這個密鑰稱為fscrypt 主密鑰。)從這個密鑰,內核派生以下子密鑰:

  • 密鑰標識符。這不用於加密,而是用於標識用於保護特定文件或目錄的密鑰的值。
  • 文件內容加密密鑰
  • 文件名加密密鑰

相比之下,下圖描述了使用硬件封裝密鑰時 FBE 的密鑰層次結構:

FBE 密鑰層次結構(使用硬件封裝的密鑰)
圖 2. FBE 密鑰層次結構(使用硬件封裝的密鑰)

與之前的情況相比,密鑰層次結構增加了一個額外的級別,並且文件內容加密密鑰已被重新定位。根節點仍然代表 Android 傳遞給 Linux 以解鎖一組加密目錄的密鑰。但是,現在該密鑰是臨時包裝的形式,並且為了使用它必須傳遞給專用硬件。該硬件必須實現兩個採用臨時包裝密鑰的接口:

  • 一個接口,用於派生inline_encryption_key並將其直接編程到內聯加密引擎的密鑰槽中。這允許在沒有軟件訪問原始密鑰的情況下對文件內容進行加密/解密。在Android通用內核中,該接口對應blk_ksm_ll_ops::keyslot_program操作,必須由存儲驅動實現。
  • 派生和返回sw_secret (“軟件機密”——在某些地方也稱為“原始機密”)的一個接口,它是 Linux 用來派生除文件內容加密之外的所有內容的子密鑰的密鑰。在Android通用內核中,該接口對應blk_ksm_ll_ops::derive_raw_secret操作,必須由存儲驅動實現。

要從原始存儲密鑰派生inline_encryption_keysw_secret ,硬件必須使用加密的強 KDF。此 KDF 必須遵循密碼學最佳實踐;它必須具有至少 256 位的安全強度,即足以用於以後使用的任何算法。在導出每種類型的子密鑰時,它還必須使用不同的標籤、上下文和/或特定於應用程序的信息字符串,以保證生成的子密鑰是加密隔離的,即知道其中一個不會洩露任何其他子密鑰。不需要密鑰拉伸,因為原始存儲密鑰已經是一個均勻隨機的密鑰。

從技術上講,可以使用任何滿足安全要求的 KDF。但是,出於測試目的,有必要在測試代碼中重新實現相同的 KDF。目前,已經審查並實施了一項 KDF;它可以在vts_kernel_encryption_test的源代碼中找到。建議硬件使用此 KDF,它使用NIST SP 800-108 “KDF in Counter Mode” ,以AES-256-CMAC作為 PRF。請注意,為了兼容,算法的所有部分必須相同,包括 KDF 上下文的選擇和每個子鍵的標籤。

密鑰包裝

為了滿足硬件封裝密鑰的安全目標,定義了兩種類型的密鑰封裝:

  • 臨時包裝:硬件使用在每次啟動時隨機生成的密鑰對原始密鑰進行加密,並且不會直接暴露在硬件之外。
  • 長期包裝:硬件使用內置在硬件中的唯一持久密鑰加密原始密鑰,該密鑰不直接暴露在硬件之外。

傳遞給 Linux 內核以解鎖存儲的所有密鑰都是臨時包裝的。這確保瞭如果攻擊者能夠從系統內存中提取正在使用的密鑰,那麼該密鑰不僅在設備外無法使用,而且在重新啟動後在設備上也將無法使用。

同時,Android 仍然需要能夠將加密版本的密鑰存儲在磁盤上,以便能夠在第一時間解鎖。原始密鑰將用於此目的。但是,最好不要讓原始密鑰出現在系統內存中,這樣即使在引導時提取它們,也永遠不能提取它們以在設備外使用。為此,定義了長期迴繞的概念。

為了支持管理以這兩種不同方式包裝的密鑰,硬件必須實現以下接口:

  • 生成和導入存儲密鑰的接口,以長期包裝的形式返回它們。這些接口是通過 KeyMint 間接訪問的,它們對應於TAG_STORAGE_KEY KeyMint 標籤。 vold使用“生成”功能生成新的存儲密鑰供 Android 使用,而vts_kernel_encryption_test使用“導入”功能導入測試密鑰。
  • 將長期包裝的存儲密鑰轉換為臨時包裝的存儲密鑰的接口。這對應於convertStorageKeyToEphemeral KeyMint 方法。 voldvts_kernel_encryption_test都使用此方法來解鎖存儲。

密鑰包裝算法是一個實現細節,但它應該使用強大的 AEAD,例如帶有隨機 IV 的 AES-256-GCM。

需要更改軟件

AOSP 已經有一個支持硬件封裝密鑰的基本框架。這包括對用戶空間組件(如vold )的支持,以及對blk-cryptofscryptdm-default-key的 Linux 內核支持。

但是,需要進行一些特定於實現的更改。

KeyMint 的變化

必須修改設備的 KeyMint 實現以支持TAG_STORAGE_KEY並實現convertStorageKeyToEphemeral方法。

在 Keymaster 中,使用exportKey而不是convertStorageKeyToEphemeral

Linux內核變化

必須修改設備內聯加密引擎的 Linux 內核驅動程序以設置BLK_CRYPTO_FEATURE_WRAPPED_KEYS ,使keyslot_program()keyslot_evict()操作支持編程/逐出硬件包裝的密鑰,並實現derive_raw_secret()操作。

測試

儘管使用硬件封裝密鑰的加密比使用標準密鑰的加密更難測試,但仍然可以通過導入測試密鑰並重新實現硬件所做的密鑰派生來進行測試。這是在vts_kernel_encryption_test中實現的。要運行此測試,請運行:

atest -v vts_kernel_encryption_test

閱讀測試日誌並驗證硬件封裝的密鑰測試用例(例如FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicyDmDefaultKeyTest.TestHwWrappedKey )由於未檢測到對硬件封裝密鑰的支持而未被跳過,因為測試結果仍將“通過”那個案子。

啟用

一旦設備的硬件封裝密鑰支持正常工作,您可以對設備的fstab文件進行以下更改,以使 Android 將其用於 FBE 和元數據加密:

  • FBE:將wrappedkey_v0標誌添加到文件fileencryption參數。例如,使用fileencryption=::inlinecrypt_optimized+wrappedkey_v0 。有關更多詳細信息,請參閱FBE 文檔
  • 元數據加密:將wrappedkey_v0標誌添加到metadata_encryption參數。例如,使用metadata_encryption=:wrappedkey_v0 。有關更多詳細信息,請參閱元數據加密文檔