第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 实现流程
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内部完成。