Android 密钥库系统

Android 密钥库系统允许您将加密密钥存储在容器中,以使其更难以从设备中提取。密钥一旦进入密钥库,即可用于加密操作,密钥材料将保持不可导出。此外,密钥库系统允许您限制何时以及如何使用密钥,例如要求对密钥使用进行用户身份验证或限制密钥仅在某些加密模式下使用。有关详细信息,请参阅安全功能部分。

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

安全功能

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

提取防护

Android 密钥库密钥的密钥材料使用两种安全措施来保护免遭提取

  • 密钥材料绝不会进入应用程序进程。当应用使用 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()的布尔返回值。

硬件安全模块

运行 Android 9(API 级别 28)或更高版本的受支持设备可以具有StrongBox Keymaster,这是驻留在类似硬件安全模块的安全元件中的 Keymaster 或 Keymint HAL 的实现。虽然硬件安全模块可以指密钥存储的许多不同实现(例如 TEE,在这些实现中,Linux 内核受损无法泄露它们),但 StrongBox 明确指代嵌入式安全元件 (eSE) 或片上安全处理单元 (iSE) 等设备。

该模块包含以下内容:

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

为了支持低功耗 StrongBox 实现,支持一部分算法和密钥大小。

  • RSA 2048
  • AES 128 和 256
  • ECDSA、ECDH P-256
  • HMAC-SHA256(支持 8 字节到 64 字节(含)之间的密钥大小)
  • 三重 DES
  • 扩展长度 APDUs
  • 密钥证明
  • 对升级的支持修正案 H

使用KeyStore类生成或导入密钥时,通过将true传递给setIsStrongBoxBacked()方法,您可以表示偏好将密钥存储在 StrongBox Keymaster 中。

虽然与 TEE 相比,StrongBox 速度稍慢且资源受限(意味着它支持的并发操作较少),但 StrongBox 提供了针对物理攻击和侧信道攻击的更好安全保障。如果您想优先考虑更高的安全保障而不是应用资源效率,我们建议在可用 StrongBox 的设备上使用它。在 StrongBox 不可用时,您的应用始终可以回退到 TEE 来存储密钥材料。

密钥使用授权

为了避免在 Android 设备上未经授权使用密钥,Android Keystore 允许应用在生成或导入密钥时指定其密钥的授权用途。密钥生成或导入后,其授权将无法更改。然后,Android Keystore 会在每次使用密钥时强制执行这些授权。这是一种高级安全功能,通常仅在您的需求是在密钥生成/导入后(但不是之前或期间)应用程序进程受到攻击不会导致密钥被未经授权使用时才有用。

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

  • 密码学:密钥只能与授权的密钥算法、操作或用途(加密、解密、签名、验证)、填充方案、分组模式或摘要一起使用。
  • 时间有效性区间:密钥仅在定义的时间段内授权使用。
  • 用户身份验证:只有在用户最近已通过身份验证时,才能使用密钥。请参阅需要用户身份验证才能使用密钥

作为密钥材料位于安全硬件内部的密钥的附加安全措施(请参阅KeyInfo.isInsideSecureHardware()或对于目标 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.getInstance(type)方法一起使用的KeyStore类型,并作为可与KeyPairGenerator.getInstance(algorithm, provider)KeyGenerator.getInstance(algorithm, provider)方法一起使用的提供程序。

由于加密操作可能非常耗时,因此应用应避免在其主线程上使用AndroidKeyStore,以确保应用的 UI 保持响应。(StrictMode可以帮助您找到并非如此的情况。)

生成新的私钥或密钥

要生成包含PrivateKey的新的KeyPair,您必须指定证书的初始 X.509 属性。您可以使用KeyStore.setKeyEntry()在以后的时间用证书颁发机构 (CA) 签名的证书替换证书。

要生成密钥对,请使用带有KeyGenParameterSpecKeyPairGenerator

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

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

支持的算法

博文

请参阅博文ICS 中统一密钥存储访问