密钥生成实战:KeyPairGeneratorSpec 与 KeyGenParameterSpec
说到在 Android 里生成 RSA 或 AES 密钥,很多人第一反应就是 KeyPairGenerator 直接一波。但如果你真这么干,往往会发现密钥根本存不进 KeyStore,或者导出时被系统卡住。嗯,这里的关键就在于——你得用对 Spec。
我个人习惯把这两个 Spec 理解成“钥匙模具”。一个模具(KeyPairGeneratorSpec)是老款,Android 4.3 到 6.0 时代的东西;另一个(KeyGenParameterSpec)是 Android 6.0 以后的新模子,功能更全,也更灵活。
为什么有两个 Spec?
说白了,KeyPairGeneratorSpec 是早期 Android 为了把密钥直接绑到硬件安全模块(TEE/SE)而设计的。它的局限性非常明显——只能生成非对称密钥对,没法控制加密填充、签名方案这些细节。
我记得在 Android 5.1 上做项目时,用 KeyPairGeneratorSpec 生成 RSA 密钥,结果发现没法指定 OAEP 填充,只能老老实实用 PKCS#1 v1.5。后来升级到 Android 6.0,KeyGenParameterSpec 一出来,这些限制全都没了。
KeyGenParameterSpec 不仅能生成 RSA 密钥对,还能生成 AES、HMAC 等对称密钥。它支持你精细控制:
- 密钥用途:加密、解密、签名、验证
- 填充方式:PKCS1、OAEP
- 加密模式:CBC、GCM、ECB
- 是否启用用户认证(指纹/锁屏)
- 密钥有效期
实战:生成 RSA 密钥对
先上代码。下面这个例子我写在好几个生产项目里,直接拿来用就行。
// 创建 KeyPairGenerator 实例
KeyPairGenerator generator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore");
// 使用 KeyGenParameterSpec 配置参数
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"my_rsa_key_123", // 密钥别名,存到 KeyStore 时用
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setKeySize(2048) // RSA 密钥长度
// .setUserAuthenticationRequired(true) // 如果需要用户认证才使用
.build();
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
注意几个坑,我曾经踩过:
- 别名重复:如果 KeyStore 里已经存在同别名的密钥,调用
generateKeyPair()会抛出KeyStoreException。我习惯先删除再生成,或者用时间戳+设备ID构造唯一别名。 - keySize 别设太小:RSA 1024 位已被部分安全标准弃用,建议 2048 起步。
- 不能导出私钥:通过 KeyStore 生成的 RSA 私钥无法用
getEncoded()导出。这是 AndroidKeyStore 的设计,私钥永远留在安全区内。你想想看,这才是真正保密的做法。
实战:生成 AES 密钥
AES 是对称密钥,不需要 KeyPair。但很多新手会混淆,以为也要用 KeyPairGenerator。其实,我们用的是 KeyGenerator 配合 KeyGenParameterSpec。
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore");
KeyGenParameterSpec aesSpec = new KeyGenParameterSpec.Builder(
"my_aes_key_456",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.setRandomizedEncryptionRequired(true) // GCM 模式推荐开启
.build();
keyGenerator.init(aesSpec);
SecretKey aesKey = keyGenerator.generateKey();
这里有个要点:AES 在 GCM 模式下不需要填充,所以 setEncryptionPaddings 要设为 NONE。如果你用 CBC 模式,则需要 PKCS7Padding。
setRandomizedEncryptionRequired(true)。这样每次加密都会使用随机 IV,防止同样的明文产生同样的密文。我在一个支付 SDK 里因为没开这个,QA 反馈加密结果居然能预测,后来才补上。
对比:KeyPairGeneratorSpec vs KeyGenParameterSpec
老规矩,上表格。看着更清楚。
| 特性 | KeyPairGeneratorSpec | KeyGenParameterSpec |
|---|---|---|
| 最低 API 级别 | API 18 | API 23 |
| 支持算法 | 仅非对称(RSA/EC) | 非对称 + 对称(AES/HMAC) |
| 加密填充指定 | 不支持 | 支持(OAEP/PKCS1 等) |
| 用户认证 | 不支持 | 支持(指纹/锁屏) |
| 密钥有效期 | 不支持 | 支持(setKeyValidityStart/End) |
| 密钥安全导出 | 私钥不可导出 | 私钥不可导出(但可设置可导出标志) |
整体流程图:密钥生成到落库
下面的 SVG 图展示了一次典型的 Android KeyStore 密钥生成流程。节点用不同颜色区分步骤,方便理解
KeyPairGeneratorSpec 生成对称密钥——它根本不支持。我见过有人把 AES 密钥的字节数组硬编码到代码里,这是最危险的做法之一。一旦 APK 被逆向,密钥等于拱手送人。永远使用 AndroidKeyStore 来保管密钥材料。
嗯,到这里,RSA 密钥对和 AES 密钥如何通过 AndroidKeyStore 安全生成,你应该已经清楚了。下次写代码时,记得先确认一下 targetSdkVersion,该用 KeyGenParameterSpec 就别偷懒用老 API。你的密钥安全,就从这一步开始。