在系統單晶片 (SoC) 中提供受信任的執行環境,可讓 Android 裝置為 Android 作業系統、平台服務,甚至第三方應用程式提供硬體支援的強大安全服務。開發人員如要使用 Android 專屬擴充功能,請前往 android.security.keystore。
在 Android 6.0 之前,Android 已提供簡單的硬體加密服務 API,由 Keymaster 硬體抽象層 (HAL) 0.2 版和 0.3 版提供。Keystore 提供數位簽署和驗證作業,以及產生及匯入非對稱式簽署金鑰組。許多裝置都已實作這項功能,但許多安全性目標都無法只透過簽名 API 輕鬆達成。Android 6.0 中的 KeyStore 擴充了 KeyStore API,提供更廣泛的功能。
在 Android 6.0 中,Keystore 新增了對稱式加密編譯原始碼、AES 和 HMAC,以及硬體支援金鑰的存取控制系統。存取權控管機制會在金鑰產生期間指定,並在金鑰的整個生命週期內強制執行。您可以限制金鑰只能在使用者完成驗證後才能使用,且只能用於特定用途或搭配特定加密編譯參數使用。詳情請參閱「授權標記」頁面。
除了擴大密碼編譯基本功能的範圍,Android 6.0 中的 Keystore 也新增了以下功能:
- 使用控制方案,允許限制金鑰用途,以減輕因濫用金鑰而導致安全性受損的風險
- 存取權控管方案,可將金鑰限制給特定使用者、用戶端和指定的時間範圍
在 Android 7.0 中,Keymaster 2 新增了金鑰認證和版本繫結的支援功能。金鑰認證會提供公開金鑰憑證,其中包含金鑰及其存取權控制項的詳細說明,以便遠端驗證金鑰是否存在於安全硬體中,以及其設定。
版本繫結會將鍵繫結至作業系統和修補程式等級版本。這可確保攻擊者在舊版系統或 TEE 軟體中發現弱點時,無法將裝置還原為有安全漏洞的版本,並使用新版建立的金鑰。此外,如果在已升級至較新版本或修補程式級別的裝置上使用特定版本和修補程式級別的金鑰,金鑰會先升級再使用,而先前版本的金鑰會失效。隨著裝置升級,金鑰會隨著裝置「鎖定」前進,但如果裝置回復至先前的版本,金鑰就會無法使用。
在 Android 8.0 中,Keymaster 3 從舊式 C 結構的硬體抽象層 (HAL) 轉換為 C++ HAL 介面,該介面是由新版硬體介面定義語言 (HIDL) 中的定義產生。在變更過程中,許多引數類型都已變更,但類型和方法與舊類型和 HAL 結構體方法一對一對應。
除了這個介面修訂版本,Android 8.0 也擴充了 Keymaster 2 的認證功能,以支援ID 認證。ID 認證提供有限的選用機制,可針對硬體 ID (例如裝置序號、產品名稱和電話 ID (IMEI / MEID)) 進行強力認證。為實作這項新增功能,Android 8.0 變更了 ASN.1 認證結構定義,以便新增 ID 認證。Keymaster 實作項目需要找到一些安全的方式來擷取相關資料項目,並定義機制,以便安全且永久停用該功能。
Android 9 的更新內容包括:
- 更新至 Keymaster 4
- 支援內嵌的安全元件
- 支援安全金鑰匯入功能
- 支援 3DES 加密
- 變更版本繫結,讓 boot.img 和 system.img 分別設定版本,以便獨立更新
詞彙解釋
以下簡要介紹 Keystore 元件及其關係。
AndroidKeystore 是應用程式用來存取 KeyStore 功能的 Android Framework API 和元件。這項功能是以標準 Java Cryptography Architecture API 的擴充功能實作,並包含在應用程式專屬程序空間中執行的 Java 程式碼。AndroidKeystore
會將應用程式要求的 Keystore 行為轉送至 Keystore 守護程序,以便完成要求。
KeyStore Daemon 是 Android 系統 Daemon,可透過 Binder API 提供對所有 KeyStore 功能的存取權。它負責儲存「金鑰 blob」,其中包含實際的密鑰內容,並經過加密,因此 Keystore 可以儲存這些內容,但無法使用或揭露這些內容。
keymasterd 是 HIDL 伺服器,可提供 Keymaster TA 的存取權。(這個名稱並未標準化,僅供概念性參考)。
Keymaster TA (受信任的應用程式) 是執行在安全環境中的軟體,通常位於 ARM SoC 的 TrustZone 中,可提供所有安全的 Keystore 作業、存取原始金鑰素材,以及驗證所有金鑰的存取控制條件等。
LockSettingsService 是 Android 系統元件,負責驗證使用者密碼和指紋。這並非 Keystore 的一部分,但由於許多 Keystore 金鑰作業都需要使用者驗證,因此與 Keystore 相關。LockSettingsService
會與 Gatekeeper TA 和 Fingerprint TA 互動,以取得驗證權杖,並將其提供給 Keystore 守護程序,最後由 Keymaster TA 應用程式使用。
Gatekeeper TA (受信任的應用程式) 是另一個在安全情境中執行的元件,負責驗證使用者密碼,並產生驗證權杖,用於向 Keymaster TA 證明在特定時間點為特定使用者完成驗證。
指紋 TA (受信任的應用程式) 是另一個在安全環境中執行的元件,負責驗證使用者指紋,並產生驗證權杖,用於向 Keymaster TA 證明在特定時間點為特定使用者完成驗證。
建築
Android Keystore API 和基礎 Keymaster HAL 提供基本但足夠的加密原始碼集,可讓您使用受控存取權的硬體金鑰實作通訊協定。
Keymaster HAL 是 OEM 提供的動態可載入程式庫,可用於 Keystore 服務,提供硬體支援的加密編譯服務。為確保安全性,HAL 實作不會在使用者空間或核心空間中執行任何敏感作業。敏感作業會委派至透過某些核心介面存取的安全處理器。產生的架構如下所示:

圖 1. 存取 Keymaster
在 Android 裝置中,Keymaster HAL 的「用戶端」由多個層級 (例如應用程式、架構、Keystore 守護程序) 組成,但為了方便說明,本文將略過這些層級。這表示所述的 Keymaster HAL API 是低階 API,由平台內部元件使用,不會公開給應用程式開發人員。如要瞭解較高層級的 API,請前往 Android 開發人員網站。
Keymaster HAL 的目的並非實作安全敏感的演算法,而是只將要求編排並解編,傳送至安全世界。線路格式是由實作定義。
與舊版的相容性
Keymaster 1 HAL 與先前發布的 HAL 完全不相容,例如 Keymaster 0.2 和 0.3。為了在搭載舊版 Keymaster HAL 的 Android 5.0 以下版本裝置上,促進互通性,Keystore 提供轉接程式,可透過對現有硬體程式庫的呼叫,實作 Keymaster 1 HAL。結果無法提供 Keymaster 1 HAL 的完整功能。特別是,它只支援 RSA 和 ECDSA 演算法,且所有金鑰授權的強制執行作業都是由轉接程式在非安全環境中執行。
Keymaster 2 移除了 get_supported_*
方法,並允許 finish()
方法接受輸入內容,進一步簡化 HAL 介面。這樣一來,在輸入內容可一次取得的情況下,就能減少往返 TEE 的次數,並簡化 AEAD 解密作業的實作方式。
在 Android 8.0 中,Keymaster 3 從舊式 C 結構 HAL 轉換至由新版硬體介面定義語言 (HIDL) 定義所產生的 C++ HAL 介面。您可以將產生的 IKeymasterDevice
類別設為子類別,並實作純虛擬方法,藉此建立新式 HAL 實作項目。在變更過程中,許多引數類型都已變更,但類型和方法與舊類型和 HAL 結構體方法一對一對應。
HIDL 總覽
硬體介面定義語言 (HIDL) 提供實作語言無關的機制,用於指定硬體介面。HIDL 工具目前支援產生 C++ 和 Java 介面。我們預期大多數受信任的執行環境 (TEE) 實作人員會認為 C++ 工具更方便,因此本頁僅討論 C++ 表示法。
HIDL 介面由一組方法組成,如下所示:
methodName(INPUT ARGUMENTS) generates (RESULT ARGUMENTS);
有各種預先定義的類型,HAL 可定義新的列舉和結構體類型。如要進一步瞭解 HIDL,請參閱「參考資料」一節。
Keymaster 3 IKeymasterDevice.hal
的範例方法如下:
generateKey(vec<KeyParameter> keyParams) generates(ErrorCode error, vec<uint8_t> keyBlob, KeyCharacteristics keyCharacteristics);
這與下列 keymaster2 HAL 的功能相同:
keymaster_error_t (*generate_key)( const struct keymaster2_device* dev, const keymaster_key_param_set_t* params, keymaster_key_blob_t* key_blob, keymaster_key_characteristics_t* characteristics);
在 HIDL 版本中,由於 dev
引數是隱含的,因此已予以移除。params
引數不再是包含指標的結構體,該指標參照 key_parameter_t
物件的陣列,而是包含 KeyParameter
物件的 vec
(向量)。傳回值會列於「generates
」子句中,包括鍵 blob 的 uint8_t
值向量。
HIDL 編譯器產生的 C++ 虛擬方法如下:
Return<void> generateKey(const hidl_vec<KeyParameter>& keyParams, generateKey_cb _hidl_cb) override;
其中 generateKey_cb
是函式指標,定義如下:
std::function<void(ErrorCode error, const hidl_vec<uint8_t>& keyBlob, const KeyCharacteristics& keyCharacteristics)>
也就是說,generateKey_cb
是會採用產生子句中列出的傳回值的函式。HAL 實作類別會覆寫這個 generateKey
方法,並呼叫 generateKey_cb
函式指標,將作業結果傳回給呼叫端。請注意,函式指標呼叫是同步的。呼叫端會呼叫 generateKey
,而 generateKey
會呼叫所提供的函式指標,該指標會執行至完成,並將控制權傳回至 generateKey
實作,然後傳回至呼叫端。
如需詳細範例,請參閱 hardware/interfaces/keymaster/3.0/default/KeymasterDevice.cpp
中的預設實作方式。預設實作項目可為搭載舊版 keymaster0、keymaster1 或 keymaster2 HAL 的裝置提供回溯相容性。
存取權控管
Keystore 存取權控管最基本的規則是,每個應用程式都有自己的命名空間。但每項規則都有例外狀況。Keystore 中有幾個硬式編碼的對應項目,可讓特定系統元件存取特定其他命名空間。這是一個非常直接的工具,可讓一個元件完全控管另一個命名空間。接著,供應商元件會以 KeyStore 的用戶端形式存在。我們目前無法為供應商元件建立命名空間,例如 WPA 申請者。
為了支援供應商元件,並在不使用硬式編碼例外狀況的情況下,將存取控制權泛用化,Keystore 2.0 引進了網域和 SELinux 命名空間。
KeyStore 網域
有了 Keystore 網域,我們就能將命名空間與 UID 分離。存取 Keystore 中金鑰的用戶端必須指定要存取的網域、命名空間和別名。根據這個元組和呼叫者的身分識別,我們可以判斷呼叫者要存取哪個金鑰,以及是否具有適當的權限。
我們引入了五個網域參數,用於控管可存取金鑰的方式。它們會控管金鑰描述元件的命名空間參數語意,以及執行存取權控管的方式。
DOMAIN_APP
:應用程式網域涵蓋舊版行為。Java 金鑰庫 SPI 預設會使用這個網域。使用這個網域時,系統會忽略命名空間引數,改為使用呼叫端的 UID。對這個網域的存取權是由 Keystore 標籤控管,該標籤屬於 SELinux 政策中的keystore_key
類別。DOMAIN_SELINUX
:這個網域表示命名空間在 SELinux 政策中具有標籤。系統會查詢並將命名空間參數轉譯為目標內容,並針對keystore_key
類別的呼叫 SELinux 內容執行權限檢查。為特定作業建立權限後,系統會使用完整元組來查詢索引鍵。DOMAIN_GRANT
:授權網域表示命名空間參數是授權 ID。系統會忽略別名參數。系統會在授權時執行 SELinux 檢查。進一步的存取權控管機制只會檢查呼叫端 UID 是否與要求授予的受贈者 UID 相符。DOMAIN_KEY_ID
:這個網域表示命名空間參數是專屬鍵 ID。金鑰本身可能已使用DOMAIN_APP
或DOMAIN_SELINUX
建立。權限檢查會在domain
和namespace
從索引鍵資料庫載入後執行,這與 blob 由網域、命名空間和別名元組載入的情況相同。建立金鑰 ID 網域的原因是為了維持一致性。透過別名存取金鑰時,後續的呼叫可以對不同的金鑰運作,因為系統可能已產生或匯入新金鑰並繫結至此別名。不過,鍵 ID 永遠不會變更。因此,在使用別名從 Keystore 資料庫載入金鑰後,如果使用者透過金鑰 ID 使用金鑰,只要金鑰 ID 仍存在,就能確定該金鑰與先前使用的金鑰相同。這項功能不會向應用程式開發人員公開。而是在 Android Keystore SPI 中使用,即使以不安全的方式同時使用,也能提供更一致的體驗。DOMAIN_BLOB
:Blob 網域表示呼叫端會自行管理 Blob。此選項適用於需要在掛載資料分割區之前存取 KeyStore 的用戶端。鍵 blob 會包含在鍵描述元資料的blob
欄位中。
透過 SELinux 網域,我們可以讓供應商元件存取非常特定的 Keystore 命名空間,這些命名空間可由系統元件 (例如設定對話方塊) 共用。
keystore_key 的 SELinux 政策
命名空間標籤會使用 keystore2_key_context
檔案進行設定。
這些檔案中的每一行都會將數字命名空間 ID 對應至 SELinux 標籤。例如:
# wifi_key is a keystore2_key namespace intended to be used by wpa supplicant and # Settings to share keystore keys. 102 u:object_r:wifi_key:s0
以這種方式設定新的鍵命名空間後,我們就能新增適當的政策來授予存取權。舉例來說,如要讓 wpa_supplicant
在新的命名空間中取得及使用鍵,我們會在 hal_wifi_supplicant.te
中加入下列行:
allow hal_wifi_supplicant wifi_key:keystore2_key { get, use };
設定新的命名空間後,AndroidKeyStore 幾乎可以照常使用。唯一的差異在於必須指定命名空間 ID。如要從 KeyStore 載入及匯入金鑰,請使用 AndroidKeyStoreLoadStoreParameter
指定命名空間 ID。例如:
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter; import java.security.KeyStore; KeyStore keystore = KeyStore.getInstance("AndroidKeyStore"); keystore.load(new AndroidKeyStoreLoadStoreParameter(102));
如要在特定命名空間中產生索引鍵,必須使用 KeyGenParameterSpec.Builder#setNamespace():
提供命名空間 ID
import android.security.keystore.KeyGenParameterSpec; KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder(); specBuilder.setNamespace(102);
您可以使用下列背景檔案設定 Keystore 2.0 SELinux 命名空間。每個分區都有不同的預留範圍,範圍內有 10,000 個命名空間 ID,以免發生衝突。
分區 | 範圍 | 設定檔 |
---|---|---|
系統 | 0 ... 9,999 | /system/etc/selinux/keystore2_key_contexts, /plat_keystore2_key_contexts |
擴充系統 | 10,000 ... 19,999 | /system_ext/etc/selinux/system_ext_keystore2_key_contexts, /system_ext_keystore2_key_contexts |
產品 | 20,000 ... 29,999 | /product/etc/selinux/product_keystore2_key_contexts, /product_keystore2_key_contexts |
供應商 | 30,000 ... 39,999 | /vendor/etc/selinux/vendor_keystore2_key_contexts, /vendor_keystore2_key_contexts |
用戶端會透過要求 SELinux 網域和所需的虛擬命名空間 (在本例中為 "wifi_key"
) 的數字 ID,來要求金鑰。
除了上述內容外,我們也定義了下列命名空間。如果這些規則取代特殊規則,下表會顯示這些規則對應的 UID。
名稱空間編號 | SEPolicy 標籤 | UID | 說明 |
---|---|---|---|
0 | su_key | 無 | 超級使用者金鑰。僅用於測試 userdebug 和 eng 版本。不適用於使用者版本。 |
1 | shell_key | 無 | 可供殼層使用的命名空間。主要用於測試,但也可以透過指令列在使用者版本中使用。 |
100 | vold_key | 無 | 供 vold 使用的值。 |
101 | odsing_key | 無 | 由裝置端簽署 Daemon 使用。 |
102 | wifi_key | AID_WIFI(1010) | 由 Android 的 Wifi 系統使用,包括 wpa_supplicant。 |
120 | resume_on_reboot_key | AID_SYSTEM(1000) | 由 Android 系統伺服器用於支援重新啟動時的繼續執行。 |
存取向量
SELinux 類別 keystore_key
已經過時,因此 verify
或 sign
等部分權限已失去意義。以下是 Keystore 2.0 強制執行的新權限組合 keystore2_key
。
權限 | 意義 |
---|---|
delete
|
從 KeyStore 移除金鑰時會檢查。 |
get_info
|
要求金鑰中繼資料時會勾選。 |
grant
|
呼叫端需要此權限,才能在目標情境中建立金鑰授權。 |
manage_blob
|
呼叫端可在指定的 SELinux 命名空間中使用 DOMAIN_BLOB ,藉此自行管理 blob。這對 vold 特別實用。 |
rebind
|
這項權限可控管別名是否能重新繫結至新鍵。插入作業需要此值,且表示先前已綁定的鍵已刪除。這是插入權限,但可更準確地擷取 KeyStore 的語意。 |
req_forced_op
|
具備這項權限的用戶端可以建立不可裁舊的運算,且除非所有運算分割區都已被不可裁舊的運算占用,否則運算建立作業不會失敗。 |
update
|
如要更新鍵的子元件,就必須提供這個參數。 |
use
|
在建立使用金鑰內容的 Keymint 作業時勾選,例如用於簽署、加密、解密。 |
use_dev_id
|
產生裝置 ID 認證等裝置識別資訊時,必須使用。 |
此外,我們會在 SELinux 安全性類別 keystore2
中,將一組非金鑰專屬的 KeyStore 權限分開:
權限 | 意義 |
---|---|
add_auth
|
驗證供應器 (例如 Gatekeeper 或 BiometricsManager) 需要此值,才能新增驗證權杖。 |
clear_ns
|
這個權限原先稱為 clear_uid,可讓非命名空間擁有者刪除該命名空間中的所有鍵。 |
list
|
系統必須依據各種屬性 (例如擁有權或授權限制) 列舉鍵。呼叫端不需要此權限來列舉自己的命名空間。這項權限由 get_info 權限涵蓋。 |
lock
|
這項權限可用於鎖定 Keystore,也就是將主金鑰逐出,讓已綁定驗證金鑰無法使用或建立。 |
reset
|
這項權限可將 Keystore 重設為原廠預設值,刪除所有對 Android 作業系統運作而言非必要的金鑰。 |
unlock
|
您必須具備這項權限,才能嘗試解鎖已繫結驗證金鑰的主金鑰。 |