Android KeyStore 系統可讓您將加密編譯金鑰儲存在容器中,讓從裝置中擷取更難擷取。金鑰儲存在 KeyStore 後,您就能將金鑰用於加密作業,其餘金鑰內容無法匯出。此外,KeyStore 系統可讓您限制金鑰的使用時機和方式,例如要求使用者驗證金鑰才能使用,或是限制金鑰只能用於特定加密編譯金鑰。詳情請參閱安全性功能一節。
Android 4.0 (API 級別 14) 中引入的 KeyChain
API 會使用 KeyStore 系統;Android 4.3 (API 級別 18) 中推出的 Android KeyStore 提供者功能,以及包含在 Jetpack 中的安全性程式庫。本文件將說明 Android KeyStore 系統的使用時機和方式。
安全性功能
Android KeyStore 系統可透過兩種方式防止金鑰內容在未經授權的情況下使用。首先,這麼做可防止從應用程式程序和 Android 裝置中完整擷取金鑰內容,降低 Android 裝置「外部」未經授權使用金鑰內容的風險。其次,KeyStore 系統可讓應用程式指定金鑰的授權用途,並在應用程式程序之外強制執行這些限制,藉此降低 Android 裝置「內」未經授權使用金鑰內容的風險。
擷取預防
系統會採用下列兩種安全措施,保護 Android KeyStore 金鑰的金鑰內容,使其無法擷取:
- 金鑰內容絕對不會進入申請程序。當應用程式使用 Android KeyStore 金鑰執行加密編譯作業時,系統會將幕後明文、密文和要簽署或驗證的訊息,傳送至執行加密作業的系統程序。如果應用程式的程序遭到入侵,攻擊者或許可以使用應用程式的金鑰,但無法擷取其金鑰內容,例如用在 Android 裝置以外的地方。
- 金鑰內容可以繫結至 Android 裝置的安全硬體,例如 Trusted Execution Environment (TEE) 或安全元件 (SE)。對金鑰啟用這項功能後,金鑰內容絕不會在安全硬體外曝露。如果 Android 作業系統遭到入侵,或是攻擊者可以讀取裝置的內部儲存空間,攻擊者或許就能在 Android 裝置上使用任何應用程式的 Android KeyStore 金鑰,但無法從裝置中擷取這些金鑰。只有在裝置的安全硬體支援特定的按鍵演算法、區塊模式、邊框間距配置,以及金鑰授權搭配使用時,系統才會啟用這項功能。
如要檢查金鑰是否已啟用這項功能,請取得金鑰的
KeyInfo
。下一個步驟視應用程式的目標 SDK 版本而定:- 如果應用程式指定的是 Android 10 (API 級別 29) 以上版本,請檢查
getSecurityLevel()
的傳回值。傳回符合KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT
或KeyProperties.SecurityLevelEnum.STRONGBOX
的值,表示鍵位於安全硬體中。 - 如果應用程式指定的是 Android 9 (API 級別 28) 以下版本,請檢查
KeyInfo.isInsideSecurityHardware()
的布林值回傳值。
- 如果應用程式指定的是 Android 10 (API 級別 29) 以上版本,請檢查
硬體安全性模組
搭載 Android 9 (API 級別 28) 以上版本的支援裝置可以擁有 StrongBox Keymaster,這是一個位於硬體安全性模組型安全元素中的 Keymaster 或 Keymint HAL 的實作。雖然硬體安全性模組可能會參照許多金鑰與儲存空間的實作方式,因為 Linux kernel 遭駭,就無法揭露這些元素,例如 TEE,而 StrongBox 會明確指像內嵌安全元件 (eSE) 或 on-SoC 安全處理單元 (iSE) 等裝置。
模組包含以下內容:
- 其專屬 CPU
- 安全的儲存空間
- 真正的隨機號碼產生器
- 利用其他機制防止套件竄改和未經授權側載應用程式
- 安全計時器
- 重新啟動的通知圖釘 (或同等功能),例如一般用途輸入/輸出 (GPIO)
為了支援低功耗 StrongBox 實作,系統支援部分演算法和鍵大小子集:
- 2048 塞爾維亞第納爾
- AES 128 和 256
- ECDSA、ECDH P-256
- HMAC-SHA256 (支援 8 個位元組和 64 個位元組之間的金鑰大小 (含首尾)
- 三度 (DES)
- 更長的 APDU
- 金鑰認證
- 修訂條款 H 支援升級
使用 KeyStore
類別產生或匯入金鑰時,請將 true
傳遞至 setIsStrongBoxBacked()
方法,指出將金鑰儲存在 StrongBox Keymaster 中的偏好設定。
與 TEE 相比, StrongBox 較慢且資源受限 (意即這項服務支援的並行作業較少),但 StrongBox 可以提供更完善的安全保證,藉此抵禦實體和旁路攻擊。如果您想優先提升安全性保證,而非應用程式資源效率,建議您在適用裝置的情況下使用 StrongBox。如果 StrongBox 無法使用,應用程式隨時可以改回使用 TEE 來儲存金鑰內容。
金鑰使用授權
為避免在 Android 裝置上未經授權使用金鑰,Android KeyStore 可讓應用程式在產生或匯入金鑰時,指定金鑰的使用授權。在產生或匯入金鑰後,就無法變更其授權。每當使用金鑰時,Android KeyStore 就會強制執行授權。這是一種進階安全性功能,通常只有在需求在金鑰產生/匯入後 (但非使用中或期間) 不得導致金鑰遭未經授權使用時,應用程式程序出現問題時才會派上用場。
支援的金鑰使用授權分成以下類別:
- 加密編譯:金鑰只能用於授權的金鑰演算法、作業或用途 (加密、解密、簽署、驗證)、邊框間距配置、區塊模式或摘要。
- 時間效力間隔:金鑰僅授權使用在指定時間間隔內。
- 使用者驗證:只有在使用者近期完成驗證時,才能使用金鑰。請參閱「要求使用者進行金鑰驗證以使用金鑰」。
安全硬體可能會針對金鑰內容位於安全硬體中的金鑰 (請參閱 KeyInfo.isInsideSecurityHardware()
;如果是指定 Android 10 (API 級別 29) 或以上版本的應用程式,請參閱 KeyInfo.getSecurityLevel()
) 採取額外的安全措施,安全硬體可能會強制執行某些金鑰使用授權 (視 Android 裝置而定)。
安全硬體通常會強制執行加密編譯和使用者驗證授權。不過,安全硬體通常不會強制執行暫時性的有效性間隔授權,因為這類授權通常沒有獨立的安全即時時鐘。
您可以使用 KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware()
查詢安全硬體是否強制執行金鑰的使用者驗證授權。
選擇鑰匙圈或 Android KeyStore 供應商
如需全系統的憑證,請使用 KeyChain
API。當應用程式透過 KeyChain
API 要求使用任何憑證時,使用者可以透過系統提供的使用者介面,選擇應用程式可存取的已安裝憑證。如此一來,多個應用程式就能在取得使用者同意的情況下,使用同一組憑證。
使用 Android KeyStore 提供者,即可讓個別應用程式商店儲存自己的憑證,而只有該應用程式可以存取。這樣一來,應用程式就能管理只有自身能使用的憑證,同時提供 KeyChain
API 針對全系統憑證提供的相同安全性優勢。使用這個方法時,使用者無須選取憑證。
使用 Android KeyStore 提供者
如要使用這項功能,請使用標準 KeyStore
和 KeyPairGenerator
或 KeyGenerator
類別,以及 Android 4.3 (API 級別 18) 中引入的 AndroidKeyStore
提供者。
AndroidKeyStore
已註冊為 KeyStore
類型,以便與 KeyStore.getInstance(type)
方法搭配使用,並做為搭配 KeyPairGenerator.getInstance(algorithm, provider)
和 KeyGenerator.getInstance(algorithm, provider)
方法搭配使用的提供者。
產生新的私密或密鑰
如要產生包含 PrivateKey
和 SecretKey
的新 KeyPair
,您必須指定自行簽署憑證的初始 X.509 屬性。
安全性程式庫提供產生有效對稱金鑰的預設實作方式,如以下程式碼片段所示:
Kotlin
// Although you can define your own key generation parameter specification, it's // recommended that you use the value specified here. val mainKey = MasterKey.Builder(applicationContext) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build()
Java
// Although you can define your own key generation parameter specification, it's // recommended that you use the value specified here. Context context = getApplicationContext(); MasterKey mainKey = new MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build();
或者,您也可以使用 KeyStore.setKeyEntry()
日後將憑證替換為憑證授權單位 (CA) 簽署的憑證。
如要產生金鑰組,請將 KeyPairGenerator
與 KeyGenParameterSpec
搭配使用:
Kotlin
/* * Generate a new EC key pair entry in the Android Keystore by * using the KeyPairGenerator API. The private key can only be * used for signing or verification and only with SHA-256 or * SHA-512 as the message digest. */ val kpg: KeyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore" ) val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder( alias, KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY ).run { setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) build() } kpg.initialize(parameterSpec) val kp = kpg.generateKeyPair()
Java
/* * Generate a new EC key pair entry in the Android Keystore by * using the KeyPairGenerator API. The private key can only be * used for signing or verification and only with SHA-256 or * SHA-512 as the message digest. */ KeyPairGenerator kpg = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); kpg.initialize(new KeyGenParameterSpec.Builder( alias, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) .build()); KeyPair kp = kpg.generateKeyPair();
將加密金鑰匯入安全的硬體
Android 9 (API 級別 28) 以上版本可讓您使用 ASN.1 編碼金鑰格式,安全地將加密金鑰匯入 KeyStore。接著,Keymaster 會解密 KeyStore 中的金鑰,這樣在裝置主機記憶體中,金鑰內容一律不會顯示為明文。這個程序可提供更多金鑰解密安全性。
如要支援將加密金鑰安全匯入 KeyStore,請完成下列步驟:
產生使用
PURPOSE_WRAP_KEY
用途的金鑰組。建議您一併為這個金鑰組新增認證。在信任的伺服器或機器上,為
SecureKeyWrapper
產生 ASN.1 訊息。包裝函式包含下列結構定義:
KeyDescription ::= SEQUENCE { keyFormat INTEGER, authorizationList AuthorizationList } SecureKeyWrapper ::= SEQUENCE { wrapperFormatVersion INTEGER, encryptedTransportKey OCTET_STRING, initializationVector OCTET_STRING, keyDescription KeyDescription, secureKey OCTET_STRING, tag OCTET_STRING }
建立
WrappedKeyEntry
物件,以位元組陣列的形式傳入 ASN.1 訊息。將此
WrappedKeyEntry
物件傳遞至接受Keystore.Entry
物件的setEntry()
超載。
使用 KeyStore 項目
您可以透過所有標準 KeyStore
API 存取 AndroidKeyStore
提供者。
列出項目
呼叫 aliases()
方法即可列出 KeyStore 中的項目:
Kotlin
/* * Load the Android KeyStore instance using the * AndroidKeyStore provider to list the currently stored entries. */ val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } val aliases: Enumeration<String> = ks.aliases()
Java
/* * Load the Android KeyStore instance using the * AndroidKeyStore provider to list the currently stored entries. */ KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); Enumeration<String> aliases = ks.aliases();
簽署及驗證資料
從 KeyStore 擷取 KeyStore.Entry
並使用 Signature
API (例如 sign()
),簽署資料:
Kotlin
/* * Use a PrivateKey in the KeyStore to create a signature over * some data. */ val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } val entry: KeyStore.Entry = ks.getEntry(alias, null) if (entry !is KeyStore.PrivateKeyEntry) { Log.w(TAG, "Not an instance of a PrivateKeyEntry") return null } val signature: ByteArray = Signature.getInstance("SHA256withECDSA").run { initSign(entry.privateKey) update(data) sign() }
Java
/* * Use a PrivateKey in the KeyStore to create a signature over * some data. */ KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); KeyStore.Entry entry = ks.getEntry(alias, null); if (!(entry instanceof PrivateKeyEntry)) { Log.w(TAG, "Not an instance of a PrivateKeyEntry"); return null; } Signature s = Signature.getInstance("SHA256withECDSA"); s.initSign(((PrivateKeyEntry) entry).getPrivateKey()); s.update(data); byte[] signature = s.sign();
同樣地,請使用 verify(byte[])
方法驗證資料:
Kotlin
/* * Verify a signature previously made by a private key in the * KeyStore. This uses the X.509 certificate attached to the * private key in the KeyStore to validate a previously * generated signature. */ val ks = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry if (entry == null) { Log.w(TAG, "Not an instance of a PrivateKeyEntry") return false } val valid: Boolean = Signature.getInstance("SHA256withECDSA").run { initVerify(entry.certificate) update(data) verify(signature) }
Java
/* * Verify a signature previously made by a private key in the * KeyStore. This uses the X.509 certificate attached to the * private key in the KeyStore to validate a previously * generated signature. */ KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); KeyStore.Entry entry = ks.getEntry(alias, null); if (!(entry instanceof PrivateKeyEntry)) { Log.w(TAG, "Not an instance of a PrivateKeyEntry"); return false; } Signature s = Signature.getInstance("SHA256withECDSA"); s.initVerify(((PrivateKeyEntry) entry).getCertificate()); s.update(data); boolean valid = s.verify(signature);
要求使用者進行金鑰驗證以使用金鑰
產生金鑰或將金鑰匯入 AndroidKeyStore
時,您可以指定金鑰只有在使用者通過驗證時才能使用。使用者是以安全螢幕鎖定憑證的子集 (圖案/PIN 碼/密碼、生物特徵辨識憑證) 進行驗證。
這項進階安全性功能通常只有在需求在金鑰產生/匯入後 (但不行之前或期間) 無法規避使用金鑰的要求,導致應用程式程序出現侵害的情況時,才適用這項功能。
如果金鑰只授權使用該金鑰 (如果使用者已通過驗證),您可以呼叫 setUserAuthenticationParameters()
,將其設為在下列其中一種模式下運作:
- 針對一段時間進行授權
- 當使用者使用指定的任一憑證進行驗證時,所有金鑰都會獲得授權,可以使用。
- 針對特定加密編譯作業期間進行授權
涉及特定金鑰的每個作業都必須由使用者個別授權。
應用程式會在
BiometricPrompt
執行個體上呼叫authenticate()
,以啟動這項程序。
針對您建立的每個金鑰,您可以選擇支援高強度生物特徵辨識憑證、螢幕鎖定憑證,或兩種類型的憑證。如要判斷使用者是否已設定應用程式金鑰所需的憑證,請呼叫 canAuthenticate()
。
如果金鑰僅支援生物特徵辨識憑證,則根據預設,每次新增生物特徵辨識註冊時,金鑰就會失效。您可以將金鑰設為在新增生物特徵辨識註冊機制時保持有效。方法是將 false
傳遞至 setInvalidatedByBiometricEnrollment()
。
進一步瞭解如何在應用程式中加入生物特徵辨識驗證功能,包括如何顯示生物特徵辨識驗證對話方塊。
支援的演算法
Cipher
KeyGenerator
KeyFactory
KeyStore
(支援與KeyGenerator
和KeyPairGenerator
相同的鍵類型)KeyPairGenerator
Mac
Signature
SecretKeyFactory
網誌文章
請參閱網誌文章: 在 ICS 中整合金鑰存放區存取權。