Android KeyStore 系統

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_ENVIRONMENTKeyProperties.SecurityLevelEnum.STRONGBOX 的值,表示鍵位於安全硬體中。
    • 如果應用程式指定的是 Android 9 (API 級別 28) 以下版本,請檢查 KeyInfo.isInsideSecurityHardware() 的布林值回傳值。

硬體安全性模組

搭載 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 提供者

如要使用這項功能,請使用標準 KeyStoreKeyPairGeneratorKeyGenerator 類別,以及 Android 4.3 (API 級別 18) 中引入的 AndroidKeyStore 提供者。

AndroidKeyStore 已註冊為 KeyStore 類型,以便與 KeyStore.getInstance(type) 方法搭配使用,並做為搭配 KeyPairGenerator.getInstance(algorithm, provider)KeyGenerator.getInstance(algorithm, provider) 方法搭配使用的提供者。

產生新的私密或密鑰

如要產生包含 PrivateKeySecretKey 的新 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) 簽署的憑證。

如要產生金鑰組,請將 KeyPairGeneratorKeyGenParameterSpec 搭配使用:

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,請完成下列步驟:

  1. 產生使用 PURPOSE_WRAP_KEY 用途的金鑰組。建議您一併為這個金鑰組新增認證。

  2. 在信任的伺服器或機器上,為 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
       }
    
  3. 建立 WrappedKeyEntry 物件,以位元組陣列的形式傳入 ASN.1 訊息。

  4. 將此 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()

進一步瞭解如何在應用程式中加入生物特徵辨識驗證功能,包括如何顯示生物特徵辨識驗證對話方塊

支援的演算法

網誌文章

請參閱網誌文章: 在 ICS 中整合金鑰存放區存取權