第4章 KeyGenParameterSpec 详解:Builder 参数、用途标记、加密/签名模式设置

KeyGenParameterSpec 是 Android Keystore 中用于配置密钥生成参数的规范类。它通过 Builder 模式构建,允许开发者精确控制密钥的用途、算法细节、有效期等属性。本章将从原理到代码,逐一拆解 Builder 参数、用途标记(Purpose)、加密/签名模式设置,并提供完整的实战流程。

4.1 核心原理:Builder 参数总览

KeyGenParameterSpec.Builder 的构造方法接收两个必选参数:密钥别名(String) 和 密钥用途(int)。其余参数通过链式调用设置。下表列出最常用的 Builder 方法及其作用:

Builder 方法参数类型说明
setKeySize(int)int密钥长度(位),例如 RSA 2048、AES 256
setKeyValidityStart(Date)Date密钥生效起始时间(null 表示立即生效)
setKeyValidityEnd(Date)Date密钥失效时间(null 表示永不过期)
setBlockModes(String...)String[]允许的分组加密模式,如 ECB、CBC、CTR、GCM
setEncryptionPaddings(String...)String[]允许的加密填充模式,如 PKCS1Padding、OAEPPadding、NoPadding
setSignaturePaddings(String...)String[]允许的签名填充模式,如 RSASSA-PKCS1-v1_5、PSS
setDigests(String...)String[]允许的消息摘要算法,如 SHA-256、SHA-384、SHA-512
setUserAuthenticationRequired(boolean)boolean是否要求用户验证(指纹/锁屏)后才能使用密钥
setInvalidatedByBiometricEnrollment(boolean)boolean新增生物识别后是否使密钥失效(默认 true)
提示: 用途标记(Purpose)不是通过 Builder 方法设置,而是作为构造参数传入。它是位掩码,可以组合多个用途。

4.2 用途标记(Purpose)详解

用途标记定义密钥在 Keystore 授信硬件中允许执行的操作。定义在 KeyProperties 类中:

  • PURPOSE_ENCRYPT (1) – 加密数据
  • PURPOSE_DECRYPT (2) – 解密数据
  • PURPOSE_SIGN (4) – 签名
  • PURPOSE_VERIFY (8) – 验签
  • PURPOSE_WRAP_KEY (16) – 密钥封装(API 28+)
  • PURPOSE_AGREE_KEY (32) – 密钥协商(API 30+,用于 ECDH)

常用组合:PURPOSE_ENCRYPT | PURPOSE_DECRYPT 用于对称/非对称加解密;PURPOSE_SIGN | PURPOSE_VERIFY 用于签名验证;PURPOSE_ENCRYPT | PURPOSE_DECRYPT | PURPOSE_SIGN | PURPOSE_VERIFY 用于同时需要加解密和签名能力的非对称密钥对。

警告: 如果用途标记未包含实际使用的操作,Keystore 会在运行时抛出 KeyStoreException:Key user not authorized for the operation。务必根据业务场景设置最小必要权限。

4.3 加密/签名模式设置

Android Keystore 要求预声明允许的加密模式、填充和摘要算法,目的是在 TEE/SE 环境中预先验证操作合法性。以下为常见配置:

算法常用模式组合
AESsetBlockModes("GCM") + setEncryptionPaddings("NoPadding")(GCM 自身不填充)
RSA 加密setEncryptionPaddings("OAEPPadding") + setDigests("SHA-256", "SHA-384")
RSA 签名setSignaturePaddings("RSASSA-PKCS1-v1_5") + setDigests("SHA-256")
ECDSA 签名setDigests("SHA-256", "SHA-384")(无需设置填充)

注意: GCM 模式需要 NoPadding,且 IV 长度固定为 12 字节。RSA 的 OAEPPadding 推荐搭配 SHA-256 或更高强度的摘要。

4.4 实现流程(代码示例)

以下代码演示如何生成一个 RSA 2048 密钥对,该密钥可用于加密、解密、签名和验签,有效期 1 年,要求用户验证后才可使用。

import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.util.Calendar;

String alias = "my_rsa_key";

Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);

KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT |
        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
    .setKeySize(2048)
    .setKeyValidityStart(start.getTime())
    .setKeyValidityEnd(end.getTime())
    .setBlockModes(KeyProperties.BLOCK_MODE_ECB, KeyProperties.BLOCK_MODE_CBC)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
    .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384)
    .setUserAuthenticationRequired(true)
    .build();

KeyPairGenerator generator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
generator.initialize(spec);
generator.generateKeyPair();

4.5 调试与验证

生成密钥后,可以通过 KeyStore 加载并检查 spec 属性:

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.Entry entry = keyStore.getEntry(alias, null);
if (entry instanceof KeyStore.PrivateKeyEntry) {
    KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry;
    // 通过 Certificate 可获取公钥,但无法直接获取 KeyGenParameterSpec
}
// 注意:KeyGenParameterSpec 不能从已生成的密钥中反查,需保留引用或重新构造
调试技巧: 在初始化 KeyPairGenerator 之前打印 spec 的各个参数,帮助检查配置是否符合预期:
Log.d("KeySpec", "Purpose: " + spec.getPurposes());
Log.d("KeySpec", "Block Modes: " + Arrays.toString(spec.getBlockModes()));

4.6 常见错误与解决方案

错误现象可能原因解决方案
生成时抛出 InvalidAlgorithmParameterException用途标记与算法不匹配(如 RSA 尝试 PURPOSE_AGREE_KEY)检查算法支持的用途,例如 ECDH 才需要 PURPOSE_AGREE_KEY
加密/解密时 KeyStoreException: Key user not authorized未设置 setUserAuthenticationRequired(true) 但未进行生物识别验证确认是否需要用户验证,若不需要则设为 false
签名时 KeyStoreException: Signature padding not supported签名填充模式未在 spec 中声明使用 setSignaturePaddings() 添加对应填充
密钥过期后操作失败未设置或设定了过短的 setKeyValidityEnd检查当前时间是否在有效期内,或移除有效期限制(设为 null)

4.7 性能与稳定性优化

  • 减少密钥长度: 非对称密钥 RSA 2048 足够安全,RSA 4096 在 Keystore 中操作会显著变慢,且部分 TEE 不支持。
  • 模式选择: AES-GCM 是推荐模式,内置认证标签,无需额外 MAC;避免使用 ECB 模式。
  • 摘要缓存: 签名操作中频繁指定摘要会触发额外哈希计算,建议在 spec 中只设置需要的摘要。
  • 生物识别频率: 如果 setUserAuthenticationRequired(true),每次使用密钥都需要用户验证,高频场景会严重影响体验,考虑使用会话超时模式(API 30+ 的 setUserAuthenticationValidityDurationSeconds)。

4.8 内联结构图:KeyGenParameterSpec 构建与使用流程

KeyGenParameterSpec 构建与使用流程 算法 & 密钥长度 用途标记 (Purpose) 加密/签名模式 有效期/用户验证 KeyGenParameterSpec .Builder.build() KeyGenParameterSpec (参数集合) KeyPairGenerator .initialize(spec) 密钥对(PrivateKey / PublicKey) 存储在 AndroidKeyStore 加解密 / 签名验签 / 密钥封装 运行时用途与模式验证 用途标记位掩码组合 模式必须与 spec 声明一致