4. 生成非对称密钥(RSA):KeyPairGenerator 用法、签名与加密用途区分、密钥用途设置
4.1 原理概述
非对称密钥(RSA)由一个公钥和一个私钥组成。公钥可以公开分发,用于加密数据或验证签名;私钥必须严格保密,用于解密数据或生成签名。在 Android Keystore 系统中,私钥永久保存在安全硬件(如 TEE 或 StrongBox)内部,应用只能通过 KeyStore API 引用私钥执行密码学操作,**无法直接读取私钥的明文**。这从根本上防止了密钥泄露。
生成 RSA 密钥对需要使用 KeyPairGenerator 类,并结合 KeyGenParameterSpec 指定算法参数、密钥大小以及密钥用途。密钥用途决定该密钥对可用于哪些操作(签名/验签、加密/解密)。合理设置密钥用途可以限制密钥的能力范围,提高安全性。
4.2 实现流程
- 获取
KeyStore实例并加载(调用KeyStore.getInstance("AndroidKeyStore")和load(null))。 - 构建
KeyGenParameterSpec对象:通过KeyGenParameterSpec.Builder设置别名、算法(RSA)、密钥大小、密钥用途、摘要算法、填充方案等。 - 初始化
KeyPairGenerator:使用KeyPairGenerator.getInstance("RSA", "AndroidKeyStore")获取实例,然后调用initialize(spec)。 - 生成密钥对:调用
generateKeyPair(),返回KeyPair对象(公钥可访问,私钥为引用)。 - 获取公钥(用于分发)和私钥引用(用于密码学操作)。
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 密钥生成流程总览
核心原则:密钥用途必须在生成前明确设置,生成后无法修改。签名密钥与加密密钥必须分开创建。