第4章 RSA密钥对生成与存储:KeyPairGeneratorSpec、用途标志、签名验证、私钥不可导出特性

4.1 原理

在Android Keystore系统中,RSA密钥对的生成通过KeyPairGenerator配合KeyPairGeneratorSpec(API 18–22)或KeyGenParameterSpec(API 23+)实现。核心安全要求是:私钥一旦生成,密钥材料永不可被应用进程直接读取——即私钥对象仅作为句柄存在,所有使用私钥的操作(签名、解密)都发生在Keystore内部(TEE/StrongBox)。公钥则可以通过Certificate.getPublicKey()安全导出。

用途标志(PURPOSE_*)约束密钥的使用场景,例如:PURPOSE_SIGN | PURPOSE_VERIFY限制密钥只能用于签名和验签。未正确设置用途标志将导致运行时KeyStoreException。

4.2 实现流程

RSA密钥对生成与存储(KeyPairGeneratorSpec) 创建Spec KeyPairGeneratorSpec 初始化KeyPairGenerator 算法RSA + Spec 生成密钥对 KeyPair 私钥(句柄) KEYS材料不可导出 公钥 + X509证书 可导出用于加密/验签 使用私钥签名 / 公钥验证 操作在Keystore内部 用途标志约束实际操作 PURPOSE_SIGN/PURPOSE_VERIFY

4.3 代码与API实现

以下示例展示在API 18–22上使用KeyPairGeneratorSpec生成RSA 2048密钥对,并设置用途标志和签名算法。

// 1. 构造KeyPairGeneratorSpec
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
    .setAlias("my_rsa_key")
    .setKeySize(2048)
    .setKeyType("RSA")
    .setPurpose(KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
    .setSignatureAlgorithm("SHA256withRSA")
    .setSubject(new X500Principal("CN=MyApp"))
    .setSerialNumber(BigInteger.ONE)
    .setStartDate(new Date())
    .setEndDate(new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L))
    .build();

// 2. 初始化KeyPairGenerator
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
kpg.initialize(spec);

// 3. 生成密钥对(私钥自动存入Keystore,不可导出)
KeyPair kp = kpg.generateKeyPair();
PublicKey publicKey = kp.getPublic();

// 4. 签名操作(私钥只作为句柄)
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(kp.getPrivate());  // 实际使用Keystore内部的私钥
signer.update(message);
byte[] signature = signer.sign();

// 5. 验证操作(公钥可导出)
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(publicKey);
verifier.update(message);
boolean valid = verifier.verify(signature);
提示: 在API 23+上请使用KeyGenParameterSpec,其提供了更细粒度的控制,如setUserAuthenticationRequired(true)和指定填充方案。

4.4 调试与验证

调试目标 方法或命令 预期结果
检查密钥是否存在 KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); ks.containsAlias("my_rsa_key") 返回 true
验证私钥不可导出 调用 kp.getPrivate().getEncoded() 抛出 UnsupportedOperationException 或返回 null
验证签名/验签 使用测试数据签名并验签 验签通过(valid == true)
查看密钥规格 adb shell dumpsys key_store 列出别名、用途、算法等元信息

4.5 部署注意事项

  • 最低API版本兼容: KeyPairGeneratorSpec需API 18+;若最小支持API 23,建议直接用KeyGenParameterSpec。
  • 存储区选择: 默认使用TEE;若需StrongBox(独立安全芯片),需在Spec中设置setIsStrongBoxBacked(true)(API 28+)。
  • 证书链: 生成的密钥对会自动包含自签名证书;生产环境建议使用CA签发的证书链替换。
  • 密钥备份策略: Keystore私钥通常随应用卸载而删除;如需跨安装保留,使用setAllowedBackup(false)防止敏感密钥被云备份。

4.6 常见错误

错误1: 未设置用途标志直接初始化Signature — 抛出KeyStoreException: Invalid purpose for key。解决方案:在Spec中明确setPurpose(...)。
错误2: 尝试通过PrivateKey.getEncoded()获取私钥字节 — 返回null或抛出异常。这不是Bug,而是Keystore的安全设计。应始终通过Signature或Cipher操作私钥。
错误3: 使用KeyPairGeneratorSpec允许setEncryptionRequired()(API 18–22),该选项会导致密钥仅在用户解锁设备后可用,若未正确处理KeyStore.UnlockUserKey可能失败。推荐在API 23+改用setUserAuthenticationRequired(true)。

4.7 性能与稳定性优化

优化方向 具体做法 收益
密钥生成时机 仅在首次启动时生成一次,之后从Keystore读取 避免重复生成影响用户体验
选择合适的密钥大小 RSA 2048足够安全;4096位会显著增加生成时间(TEE内可能超1秒) 平衡安全与性能
批量签名时复用Signature对象 每次调用sign()后重置Signature实例 减少对象创建和GC开销
避免锁屏后立即操作 对于设置了用户认证的密钥,优先在解锁后不久使用,避免频繁弹出认证 提升用户体验
异步生成密钥 将generateKeyPair()放到后台线程,防止阻塞UI 防止ANR
总结: KeyPairGeneratorSpec是Android早期Keystore API中生成RSA密钥对的标准方式。尽管已被KeyGenParameterSpec取代,理解其设计(用途标志、私钥不可导出)对兼容旧版本或迁移代码至关重要。始终牢记:私钥不可导出是所有安全操作的根基,签名验证务必在Keystore内部完成。