第4章:生成RSA密钥对并存入Keystore
4.1 RSA密钥对生成原理
Android Keystore系统允许应用在硬件安全模块(TEE/StrongBox)中生成与存储RSA密钥对,私钥永不离开安全环境。密钥生成方式因API级别而异:
- API 18-22(Android 4.3~5.1):使用
KeyPairGeneratorSpec,由KeyPairGenerator通过AndroidKeyStoreProvider初始化。 - API 23+(Android 6.0+):使用
KeyGenParameterSpec.Builder,提供更精细的加密参数控制。
兼容建议:若minSdkVersion低于23,需在运行时检测版本并分别调用两种API。推荐统一使用
KeyGenParameterSpec(API 23+),低版本回退至KeyPairGeneratorSpec。
4.2 关键配置参数对比
| 参数 | KeyPairGeneratorSpec (API 18-22) | KeyGenParameterSpec (API 23+) |
|---|---|---|
| keySize | setKeySize(2048) | setKeySize(2048) |
| 算法 | 默认RSA,无需指定 | KeyProperties.KEY_ALGORITHM_RSA |
| 摘要/签名 | setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4)) | setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); setDigests(KeyProperties.DIGEST_SHA256) |
| 加密填充 | 通过Cipher调用时指定 | setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); 或EVP_OAEP |
| 密钥用途 | setKeyType("RSA") | setKeyPurpose(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) |
| 自签名证书 | 必须设置setSubject()和setStartDate/EndDate | 无需证书,Keystore自动生成 |
| 别名 | 生成时通过KeyStore.setEntry()传入 | 生成KeyPair时直接指定 |
4.3 存储别名设计规范
别名(alias)是Keystore中唯一标识密钥对的字符串。不良设计会导致命名冲突或管理混乱。推荐分层命名:
// 通用格式
[应用包名]_[用户ID/账户]_[用途]_[密钥版本]
// 示例
com.example.bluetooth_app_uid1001_data_enc_v1
com.example.bluetooth_app_uid1001_signing_v1
避免常见陷阱:不要使用纯数字或动态ID作为别名开头(如“123456”),优先使用反向域名前缀。别名在
KeyStore.deleteEntry(alias)前不可重复。
4.4 代码实现:生成RSA密钥对并存入Keystore
方案一:兼容低版本(API 18-22)
private void generateRsaKeyCompat(String alias) throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 20); // 密钥有效期20年
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(alias)
.setSubject(new X500Principal("CN=" + alias))
.setSerialNumber(BigInteger.valueOf(1))
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.setKeySize(2048)
.build();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
generator.initialize(spec);
generator.generateKeyPair();
}
方案二:API 23+ 推荐方式
private void generateRsaKey(String alias) throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(2048)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB) // RSA无需分组,但需指定
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setDigests(KeyProperties.DIGEST_SHA256)
.setInvalidatedByBiometricEnrollment(false) // 指纹改变不失效(可选)
.build();
generator.initialize(spec);
generator.generateKeyPair();
}
4.5 使用AES加密封装RSA(混合加密)
RSA算法无法直接加密超过密钥长度的数据(2048位RSA最多加密256字节)。实际蓝牙数据传输需混合加密:
- 随机生成AES-256密钥(对称密钥)
- 用AES加密蓝牙数据(支持任意长度)
- 用RSA公钥加密AES密钥
- 将加密后的AES密钥和密文一起发送
- 接收方用RSA私钥解密出AES密钥,再用AES解密数据
实现要点:加密AES密钥时使用RSA/ECB/OAEPWithSHA-256AndMGF1Padding(推荐)。AES加密使用GCM模式以保证完整性校验。
public byte[] encryptWithRsaWrappedAes(byte[] plainData, PublicKey rsaPublicKey) throws Exception {
// 1. 生成随机AES密钥
KeyGenerator aesGenerator = KeyGenerator.getInstance("AES");
aesGenerator.init(256); // AES-256
SecretKey aesKey = aesGenerator.generateKey();
// 2. 用AES-GCM加密数据
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] iv = aesCipher.getIV();
byte[] cipherData = aesCipher.doFinal(plainData);
byte[] gcmTag = Arrays.copyOfRange(cipherData, cipherData.length - 16, cipherData.length);
// 3. 用RSA公钥加密AES密钥
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());
// 4. 组装包:[len(encryptedKey=2字节)][encryptedKey][iv(12字节)][密文][tag(16字节)]
ByteArrayOutputStream output = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(output);
dos.writeShort(encryptedAesKey.length);
dos.write(encryptedAesKey);
dos.write(iv);
dos.write(cipherData); // 包含tag
return output.toByteArray();
}
注意:Android Keystore中的RSA私钥不支持直接导出。解密时需通过
KeyStore.getEntry(alias, null)获取PrivateKeyEntry,然后使用Cipher指定PRIVATE_KEY模式解密。不要假设私钥可以用getEncoded()获取。
4.6 本章核心流程图
4.7 部署与最佳实践
- 密钥有效期:生产环境建议设置5-10年,无需使用无限期。可预留
setEndDate()以便后续轮换。 - 别名管理:在
SharedPreferences中持久化别名索引,避免硬编码字符串。 - 异常处理:
KeyStoreException常见于别名重复、证书算法不匹配。建议在生成前调用keyStore.containsAlias(alias)检查。 - 性能优化:RSA密钥对生成在硬件安全环境中可能耗时1-3秒,建议在子线程中执行,并显示进度。
常见错误:使用
KeyPairGeneratorSpec时忘记设置setSubject()会导致KeyStoreException;使用KeyGenParameterSpec时未设置setBlockModes和setEncryptionPaddings会导致InvalidAlgorithmParameterException。请严格参考表4-2进行配置。