Android Keystore 系统

Android Keystore 系统允许您将加密密钥存储在容器中,从而更难以将其从设备中提取出来。一旦密钥位于 Keystore 中,您就可以将它们用于加密操作,并且密钥材料仍然不可导出。此外,Keystore 系统允许您限制密钥的使用时间与方式,例如要求用户身份验证才能使用密钥,或限制密钥只能在某些加密模式下使用。如需了解详情,请参阅安全功能部分。

Keystore 系统由 Android 4.0(API 级别 14)中引入的 KeyChain API 以及 Android 4.3(API 级别 18)中引入的 Android Keystore 提供商功能使用。本文档介绍了何时以及如何使用 Android Keystore 系统。

安全功能

Android Keystore 系统通过两种方式保护密钥材料免受未经授权的使用。首先,它通过阻止从应用进程和整个 Android 设备中提取密钥材料,从而降低了从 Android 设备外部未经授权使用密钥材料的风险。其次,Keystore 系统通过让应用指定其密钥的授权用途,然后在应用进程之外强制执行这些限制,从而降低了 Android 设备内部未经授权使用密钥材料的风险。

提取防范

Android Keystore 密钥的密钥材料通过两种安全措施防止提取

  • 密钥材料永远不会进入应用进程。当应用使用 Android Keystore 密钥执行加密操作时,在幕后,明文、密文以及待签名或验证的消息会提供给执行加密操作的系统进程。如果应用的进程被入侵,攻击者可能能够使用应用的密钥,但无法提取其密钥材料(例如,用于 Android 设备之外)。
  • 密钥材料可以绑定到 Android 设备的可信执行环境 (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() 的布尔返回值。

StrongBox KeyMint 安全芯片

搭载 Android 9(API 级别 28)或更高版本的设备可以包含 StrongBox KeyMint,这是由 StrongBox 支持的 KeyMint HAL 实现。虽然硬件安全模块 (HSM) 广义上指抗 Linux 内核入侵的安全密钥存储解决方案,但 StrongBox 特指嵌入式 SE 或集成安全区 (iSE) 中的实现,与 TEE 相比,它提供了更强的隔离性和防篡改性。

StrongBox KeyMint 的实现必须包含以下内容

  • 自己的 CPU
  • 安全存储
  • 真随机数生成器
  • 其他抵御软件包篡改和未经授权的应用旁加载的机制
  • 安全计时器
  • 重启通知引脚(或等效引脚),例如通用输入/输出 (GPIO)

支持部分算法和密钥大小,以适应低功耗 StrongBox 实现

  • RSA 2048
  • AES 128 和 256
  • ECDSA、ECDH P-256
  • HMAC-SHA256(支持 8 到 64 字节(含)之间的密钥大小)
  • 三重 DES
  • 扩展长度 APDU

StrongBox 还支持密钥证明

使用 StrongBox KeyMint

使用 FEATURE_STRONGBOX_KEYSTORE 检查设备上是否提供 StrongBox。如果 StrongBox 可用,您可以通过将 true 传递给以下方法来指示将密钥存储在 StrongBox KeyMint 中:

如果 StrongBox KeyMint 不支持指定的算法或密钥大小,框架将抛出 StrongBoxUnavailableException。如果发生这种情况,请在不调用 setIsStrongBoxBacked(true) 的情况下生成或导入密钥。

密钥使用授权

为避免在 Android 设备上未经授权使用密钥,Android Keystore 允许应用在生成或导入密钥时指定其密钥的授权用途。密钥生成或导入后,其授权无法更改。然后,每当使用密钥时,Android Keystore 都会强制执行这些授权。这是一项高级安全功能,通常仅在以下情况下有用:您的要求是,在密钥生成/导入后(但不是在此之前或期间)对您的应用进程进行破坏不能导致未经授权地使用密钥。

支持的密钥使用授权分为以下几类

  • 加密:密钥只能与授权的密钥算法、操作或用途(加密、解密、签名、验证)、填充方案、块模式或摘要一起使用。
  • 时间有效期:密钥只能在指定的时间间隔内使用。
  • 用户身份验证:密钥只能在用户近期完成身份验证后才能使用。请参阅要求用户身份验证才能使用密钥

对于密钥材料位于安全硬件内的密钥(请参阅 KeyInfo.isInsideSecurityHardware() 或对于面向 Android 10 (API 级别 29) 或更高版本的应用,请参阅 KeyInfo.getSecurityLevel()),一些密钥使用授权可能会由安全硬件强制执行,具体取决于 Android 设备。安全硬件通常会强制执行加密和用户身份验证授权。然而,安全硬件通常不会强制执行时间有效期授权,因为它通常没有独立的、安全的实时时钟。

您可以使用 KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware() 查询密钥的用户身份验证授权是否由安全硬件强制执行。

在钥匙串和 Android Keystore 提供商之间选择

当您需要系统范围的凭据时,请使用 KeyChain API。当应用通过 KeyChain API 请求使用任何凭据时,用户可以通过系统提供的 UI 选择应用可以访问哪些已安装的凭据。这允许多个应用在用户同意的情况下使用同一组凭据。

使用 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) 方法一起使用。

由于加密操作可能非常耗时,应用应避免在其主线程上使用 AndroidKeyStore,以确保应用的 UI 保持响应。(StrictMode 可以帮助您找出不符合此要求的地方。)

生成新的私钥或密钥

要生成包含 PrivateKey 的新 KeyPair,您必须指定证书的初始 X.509 属性。您可以稍后使用 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 编码的密钥格式将加密密钥安全地导入到密钥库中。然后,Keymaster 会在密钥库中解密密钥,因此密钥内容绝不会以明文形式出现在设备的宿主内存中。此过程提供了额外的密钥解密安全性。

要支持将加密密钥安全导入到密钥库中,请完成以下步骤

  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 API 访问 AndroidKeyStore 提供商。

列出条目

通过调用 aliases() 方法列出密钥库中的条目

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.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()

了解如何将生物识别身份验证功能添加到您的应用中,包括如何显示生物识别身份验证对话框

支持的算法

后续步骤