Android 密钥库系统

利用 Android 密钥库系统,您可以在容器中存储加密密钥,从而提高从设备中提取密钥的难度。在密钥进入密钥库后,可以将它们用于加密操作,而密钥材料仍不可导出。此外,它提供了密钥使用的时间和方式限制措施,例如要求进行用户身份验证才能使用密钥,或者限制为只能在某些加密模式中使用。如需了解详情,请参阅安全功能部分。

密钥库系统由 Android 4.0(API 级别 14)中引入的 KeyChain API、Android 4.3(API 级别 18)中引入的 Android 密钥库提供程序功能以及作为 Jetpack 的一部分提供的 Security 库使用。本文介绍何时以及如何使用 Android 密钥库提供程序。

安全功能

Android 密钥库系统可以保护密钥材料免遭未经授权的使用。首先,Android 密钥库可以防止从应用进程和 Android 设备中整体提取密钥材料,从而避免了在 Android 设备之外以未经授权的方式使用密钥材料。其次,Android 密钥库可以让应用指定密钥的授权使用方式,并在应用进程之外强制实施这些限制,从而避免了在 Android 设备上以未经授权的方式使用密钥材料。

提取防范

Android 密钥库密钥使用两项安全措施来避免密钥材料被提取:

  • 密钥材料永不进入应用进程。通过 Android 密钥库密钥执行加密操作时,应用会在后台将待签署或验证的明文、密文和消息馈送到执行加密操作的系统进程。如果应用进程受到攻击,攻击者也许能使用应用密钥,但无法提取密钥材料(例如,在 Android 设备以外使用)。
  • 您可以将密钥材料绑定至 Android 设备的安全硬件,例如可信执行环境 (TEE) 和安全元件 (SE)。为密钥启用此功能时,其密钥材料永远不会暴露于安全硬件之外。如果 Android 操作系统受到攻击或者攻击者可以读取设备内部存储空间,攻击者也许能在 Android 设备上使用任意应用的 Android 密钥库,但无法从设备上提取这些数据。只有设备的安全硬件支持密钥算法、分块模式、填充方案和密钥有权配合使用的摘要的特定组合时,才可启用此功能。要检查是否为密钥启用了此功能,请获取密钥的 KeyInfo 并检查 KeyInfo.isInsideSecurityHardware() 的返回值。

硬件安全模块

运行 Android 9(API 级别 28)或更高版本的受支持设备可拥有 StrongBox Keymaster,它是位于硬件安全模块中的 Keymaster HAL 的一种实现。该模块包含以下组成部分:

  • 自己的 CPU。
  • 安全存储空间。
  • 真实随机数生成器。
  • 可抵御软件包篡改和未经授权旁加载应用的附加机制。

检查存储在 StrongBox Keymaster 中的密钥时,系统会通过可信执行环境 (TEE) 证实密钥的完整性。

为支持低能耗的 StrongBox 实现,为一部分算法和密钥大小提供了支持:

  • RSA 2048
  • AES 128 和 256
  • ECDSA P-256
  • HMAC-SHA256(支持 8-64 字节密钥大小,含首末值)
  • Triple DES 168

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

密钥使用授权

为了避免在 Android 设备上以未经授权的方式使用密钥,在生成或导入密钥时,Android 密钥库会让应用指定密钥的授权使用方式。一旦生成或导入密钥,其授权将无法更改。然后,每次使用密钥时,都会由 Android 密钥库强制执行授权。这是一项高级安全功能,通常仅用于有以下要求的情形:在生成/导入密钥后(而不是之前或当中),应用进程受到攻击不会导致密钥以未经授权的方式使用。

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

  • 加密:授权密钥算法、运算或目的(加密、解密、签署、验证)、填充方案、分块模式以及可与密钥搭配使用的摘要;
  • 时间有效性间隔:密钥获得使用授权的时间间隔;
  • 用户身份验证:密钥只能在用户最近进行身份验证时使用。请参阅要求进行用户身份验证才能使用密钥

作为一项额外的安全措施,对于密钥材料位于安全硬件内的密钥(请参阅 KeyInfo.isInsideSecurityHardware()),某些密钥使用授权可能会由安全硬件强制执行,具体取决于 Android 设备。加密和用户身份验证授权可能由安全硬件强制执行。由于安全硬件一般不具备独立的安全实时时钟,时间有效性间隔授权不可能由其强制执行。

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

选择密钥链或 Android 密钥库提供程序

如果您需要系统全局凭据,请使用 KeyChain API。在应用通过 KeyChain API 请求使用任何凭据时,用户需要通过系统提供的界面选择应用可以访问已安装的哪些凭据。因此,在用户同意的情况下多个应用可以使用同一套凭据。

使用 Android 密钥库提供程序让各个应用存储自己的凭据,并且只允许应用自身访问。通过这种方式,应用可以管理仅可供自身使用的凭据,同时所提供的安全优势还可媲美 KeyChain API 为系统全局凭据提供的安全优势。这一方法不需要用户选择凭据。

使用 Android 密钥库提供程序

如需使用此功能,请使用标准的 KeyStoreKeyPairGeneratorKeyGenerator 类,以及在 Android 4.3(API 级别 18)中引入的 AndroidKeyStore 提供程序。

AndroidKeyStore 在与KeyStore.getInstance(type) 方法搭配使用时注册为 KeyStore 类型,在与 KeyPairGenerator.getInstance(algorithm, provider)KeyGenerator.getInstance(algorithm, provider) 方法搭配使用时注册为提供程序。

生成新私钥

生成新的 PrivateKey 还需要指定自签名证书具备的初始 X.509 属性。

Security 库为生成有效的对称密钥提供了默认实现,如以下代码段所示:

Kotlin

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
    val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
    

Java

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
    String masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);
    

或者,您也可以稍后使用 KeyStore.setKeyEntry 将证书替换为由证书授权机构 (CA) 签名的证书。

如需生成密钥,请使用 KeyPairGeneratorKeyPairGeneratorSpec

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

生成新密钥

如需生成密钥,请按照与生成新私钥相同的流程进行操作。在每种情况下,您都需要使用 Security 库

更安全地导入加密密钥

借助 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() 的重载。

使用密钥库条目

AndroidKeyStore 提供程序的使用通过所有标准 KeyStore API 加以实现。

列出条目

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

Kotlin

    /*
     * Load the Android KeyStore instance using the
     * "AndroidKeyStore" provider to list out what entries are
     * currently stored.
     */
    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 out what entries are
     * currently stored.
     */
    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 PrivateKey in our
     * KeyStore. This uses the X.509 certificate attached to our
     * 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 PrivateKey in our
     * KeyStore. This uses the X.509 certificate attached to our
     * 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 码/密码、指纹)的子集进行身份验证。

这是一项高级安全功能,通常仅用于有以下要求的情形:在生成/导入密钥后(而不是之前或当中),应用进程受到攻击不会导致密钥被未经身份验证的用户使用。

如果仅授权经过身份验证的用户使用密钥,可将密钥配置为以下列两种模式之一运行:

  • 经过身份验证的用户可以在一段时间内使用密钥。在用户解锁安全锁定屏幕或使用 KeyguardManager.createConfirmDeviceCredentialIntent 流确认其安全锁定屏幕凭据后,即授权其使用此模式中的所有密钥。每个密钥的授权持续时间各不相同,并由 setUserAuthenticationValidityDurationSeconds 在密钥生成或导入时指定。此类密钥只有在启用了安全锁定屏幕的情况下才能生成或导入(请参阅 KeyguardManager.isDeviceSecure())。在安全锁定屏幕停用(重新配置为“无”、“滑动”或不验证用户身份的其他模式)或被强制重置(例如由设备管理员执行)时,这些密钥将永久失效。
  • 用户身份验证会授权与某一密钥关联的特定加密操作。在此模式中,涉及此类密钥的每个操作都必须由用户单独授权。目前,此类授权的唯一方式是指纹身份验证:FingerprintManager.authenticate。此类密钥只有在至少注册一个指纹的情况下才能生成或导入(请参阅 FingerprintManager.hasEnrolledFingerprints)。一旦注册新指纹或取消注册所有指纹,这些密钥将永久失效。

支持的算法

Cipher

算法 提供支持的 API 级别 备注
AES/CBC/NoPadding 23+
AES/CBC/PKCS7Padding 23+
AES/CTR/NoPadding 23+
AES/ECB/NoPadding 23+
AES/ECB/PKCS7Padding 23+
AES/GCM/NoPadding 23+ 仅支持 12 字节长的 IV。
RSA/ECB/NoPadding 18+
RSA/ECB/PKCS1Padding 18+
RSA/ECB/OAEPWithSHA-1AndMGF1Padding 23+
RSA/ECB/OAEPWithSHA-224AndMGF1Padding 23+
RSA/ECB/OAEPWithSHA-256AndMGF1Padding 23+
RSA/ECB/OAEPWithSHA-384AndMGF1Padding 23+
RSA/ECB/OAEPWithSHA-512AndMGF1Padding 23+
RSA/ECB/OAEPPadding 23+

KeyGenerator

算法 提供支持的 API 级别 备注
AES 23+ 支持的大小:128、192、256
HmacSHA1 23+
  • 支持的大小:8-1024(含),必须是 8 的倍数
  • 默认大小:160
HmacSHA224 23+
  • 支持的大小:8-1024(含),必须是 8 的倍数
  • 默认大小:224
HmacSHA256 23+
  • 支持的大小:8-1024(含),必须是 8 的倍数
  • 默认大小:256
HmacSHA384 23+
  • 支持的大小:8-1024(含),必须是 8 的倍数
  • 默认大小:384
HmacSHA512 23+
  • 支持的大小:8-1024(含),必须是 8 的倍数
  • 默认大小:512

KeyFactory

算法 提供支持的 API 级别 备注
EC 23+ 支持的密钥规范:KeyInfo(仅私钥)、ECPublicKeySpec(仅公钥)、X509EncodedKeySpec(仅公钥)
RSA 23+ 支持的密钥规范:KeyInfo(仅私钥)、RSAPublicKeySpec(仅公钥)、X509EncodedKeySpec(仅公钥)

KeyStore

KeyStore 支持的密钥类型与 KeyPairGeneratorKeyGenerator 支持的相同。

KeyPairGenerator

算法 提供支持的 API 级别 备注
DSA 19-22
EC 23+
  • 支持的大小:224、256、384、521
  • 支持的命名曲线:P-224 (secp224r1)、P-256(又称为 secp256r1 和 prime256v1)、P-384(又称为 secp384r1)、P-521(又称为 secp521r1)

在 API 级别 23 前,EC 密钥可使用经“RSA”算法初始化的 KeyPairGeneratorSpec 的 KeyPairGenerator 生成,其密钥类型需使用 setKeyType(String) 设为“EC”。无法使用此方法指定 EC 曲线名称。系统会根据请求的密钥大小自动选择 NIST P 曲线。

RSA 18+
  • 支持的大小:512、768、1024、2048、3072、4096
  • 支持的公开指数:3、65537
  • 默认公开指数:65537

Mac

算法 提供支持的 API 级别 备注
HmacSHA1 23+
HmacSHA224 23+
HmacSHA256 23+
HmacSHA384 23+
HmacSHA512 23+

Signature

算法 提供支持的 API 级别 备注
MD5withRSA 18+
NONEwithECDSA 23+
NONEwithRSA 18+
SHA1withDSA 19-22
SHA1withECDSA 19+
SHA1withRSA 18+
SHA1withRSA/PSS 23+
SHA224withDSA 20-22
SHA224withECDSA 20+
SHA224withRSA 20+
SHA224withRSA/PSS 23+
SHA256withDSA 19-22
SHA256withECDSA 19+
SHA256withRSA 18+
SHA256withRSA/PSS 23+
SHA384withDSA 19-22
SHA384withECDSA 19+
SHA384withRSA 18+
SHA384withRSA/PSS 23+
SHA512withDSA 19-22
SHA512withECDSA 19+
SHA512withRSA 18+
SHA512withRSA/PSS 23+

SecretKeyFactory

算法 提供支持的 API 级别 备注
AES 23+ 支持的密钥规范:KeyInfo
HmacSHA1 23+ 支持的密钥规范:KeyInfo
HmacSHA224 23+ 支持的密钥规范:KeyInfo
HmacSHA256 23+ 支持的密钥规范:KeyInfo
HmacSHA384 23+ 支持的密钥规范:KeyInfo
HmacSHA512 23+ 支持的密钥规范:KeyInfo

博客文章

请参阅博客文章在 ICS 中统一密钥库访问