4. 生成非对称密钥(RSA):KeyPairGenerator 用法、签名与加密用途区分、密钥用途设置

4.1 原理概述

非对称密钥(RSA)由一个公钥和一个私钥组成。公钥可以公开分发,用于加密数据或验证签名;私钥必须严格保密,用于解密数据或生成签名。在 Android Keystore 系统中,私钥永久保存在安全硬件(如 TEE 或 StrongBox)内部,应用只能通过 KeyStore API 引用私钥执行密码学操作,**无法直接读取私钥的明文**。这从根本上防止了密钥泄露。

生成 RSA 密钥对需要使用 KeyPairGenerator 类,并结合 KeyGenParameterSpec 指定算法参数、密钥大小以及密钥用途。密钥用途决定该密钥对可用于哪些操作(签名/验签、加密/解密)。合理设置密钥用途可以限制密钥的能力范围,提高安全性。

4.2 实现流程

  1. 获取 KeyStore 实例并加载(调用 KeyStore.getInstance("AndroidKeyStore") 和 load(null))。
  2. 构建 KeyGenParameterSpec 对象:通过 KeyGenParameterSpec.Builder 设置别名、算法(RSA)、密钥大小、密钥用途、摘要算法、填充方案等。
  3. 初始化 KeyPairGenerator:使用 KeyPairGenerator.getInstance("RSA", "AndroidKeyStore") 获取实例,然后调用 initialize(spec)。
  4. 生成密钥对:调用 generateKeyPair(),返回 KeyPair 对象(公钥可访问,私钥为引用)。
  5. 获取公钥(用于分发)和私钥引用(用于密码学操作)。

4.3 核心代码示例

以下示例生成一个可用于签名的 RSA 2048 位密钥对:

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

String alias = "my_rsa_key";
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
    .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
    .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS)
    .build();

KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
// 私钥引用可直接用于签名操作

若密钥对用于加密/解密,则将用途设置为 PURPOSE_ENCRYPT | PURPOSE_DECRYPT,并指定加密填充方案:

KeyGenParameterSpec encryptSpec = new KeyGenParameterSpec.Builder(
        "my_rsa_encrypt_key",
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
    .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
    .setDigests(KeyProperties.DIGEST_SHA256)
    .build();

4.4 签名与加密用途区分

用途属性适用操作典型算法密钥可以做什么
PURPOSE_SIGN签名(私钥)RSA-SSA-PKCS1-v1_5, RSA-PSS生成签名
PURPOSE_VERIFY验签(公钥)与签名算法对应验证签名
PURPOSE_ENCRYPT加密(公钥)RSA-OAEP, RSA-PKCS1加密数据
PURPOSE_DECRYPT解密(私钥)与加密算法对应解密数据
关键约束:同一对 RSA 密钥不能同时用于签名和加密。Android Keystore 强制要求用途必须明确区分。如果尝试使用设置了 PURPOSE_SIGN 的密钥执行加密操作,Keystore 会抛出 InvalidKeyException。设计时务必按应用场景选择合适的用途组合。

4.5 密钥用途设置详解

KeyGenParameterSpec.Builder 支持使用位掩码组合多个用途。常用 API 如下:

  • setKeyPurpose(int purposeFlags) – 设置主要用途(签名/验证/加密/解密)。
  • setDigests(String... digests) – 指定签名或加密时使用的摘要算法,例如 DIGEST_SHA256。
  • setSignaturePaddings(String... paddings) – 签名填充方案,常用 SIGNATURE_PADDING_RSA_PSS 或 SIGNATURE_PADDING_RSA_PKCS1。
  • setEncryptionPaddings(String... paddings) – 加密填充方案,常用 ENCRYPTION_PADDING_RSA_OAEP。
  • setUserAuthenticationRequired(boolean) – 是否需要用户生物识别解锁才能使用私钥。
  • setStrongBoxBacked(boolean) – 是否强制使用 StrongBox 安全芯片(需硬件支持)。

4.6 调试与验证

生成密钥后,可以通过 KeyStore.getEntry(alias, null) 获取 PrivateKeyEntry,并检查密钥是否真正存储在 Keystore 中:

KeyStore.Entry entry = keyStore.getEntry(alias, null);
if (entry instanceof KeyStore.PrivateKeyEntry) {
    PrivateKey privateKey = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
    // 注意:privateKey 是 AndroidKeyStoreRSAPrivateKey 类型,实际操作由硬件完成
    Log.d("Keystore", "密钥已安全存储,算法:" + privateKey.getAlgorithm());
}

调试阶段常见问题:

  • 密钥别名冲突:若重复生成相同别名的密钥,会覆盖旧密钥(需先删除)。
  • 用途不匹配:尝试使用仅允许签名的密钥进行加密操作会抛出 KeyStoreException。
  • 缺失摘要/填充:未设置 setDigests 或 setSignaturePaddings 可能导致初始化 Cipher/Signature 时失败。

4.7 常见错误与避坑

错误模式原因解决方案
InvalidAlgorithmParameterException: algorithm only supports ...密钥大小不合法(如 RSA 低于 512 位)使用 2048 或 4096 位
KeyStoreException: key not found别名不存在或已删除确保生成时别名一致,并检查 keyStore.containsAlias()
SignatureException: invalid padding签名/加密时使用的填充方案与生成时不匹配在 KeyGenParameterSpec 中统一设置填充
NoSuchAlgorithmException: KeyPairGenerator RSA implementation not found设备无硬件支持的 RSA 实现(极少见)降级到软件实现(不推荐)或更换设备

4.8 性能与稳定性优化

  • 密钥大小选择:RSA 2048 位在安全性和性能间取得平衡;4096 位提供更高安全强度,但生成和计算耗时增加约 3-5 倍,适合高安全场景。
  • StrongBox 支持:对于敏感密钥,启用 setStrongBoxBacked(true) 会使用独立安全芯片,但会降低性能(通常慢 2-4 倍)。仅在需要物理防篡改时启用。
  • 缓存公钥:公钥可安全读取,建议在内存中缓存 PublicKey 对象,避免每次操作都从 Keystore 重新获取。
  • 一次生成,终身使用:RSA 密钥对生成是相对耗时的操作(100-500ms),建议在应用安装或首次启动时生成,而非每次操作都生成。

4.9 密钥生成流程总览

核心原则:密钥用途必须在生成前明确设置,生成后无法修改。签名密钥与加密密钥必须分开创建。
RSA 密钥生成流程 · 密钥用途决策 选择密钥用途 SIGN / ENCRYPT 构建 KeyGenParameterSpec 算法RSA, 2048位, 填充, 摘要 初始化 KeyPairGenerator provider="AndroidKeyStore" generateKeyPair() 私钥留硬件,公钥可读 签名 / 加密操作 Signature / Cipher 捕获异常 InvalidKeyException 用途不可修改 生成时需确定