密钥生成实战: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
  • 是否启用用户认证(指纹/锁屏)
  • 密钥有效期
一句话总结:如果你的 minSdkVersion >= 23,务必使用 KeyGenParameterSpec;只有需要兼容更低版本时,才考虑 KeyPairGeneratorSpec。

实战:生成 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。

小技巧:生成 AES 密钥时,最好同时启用 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 密钥生成流程。节点用不同颜色区分步骤,方便理解

Android KeyStore 密钥生成流程图 应用层请求 调用 KeyStore 入口 选择 Spec 判断 API 级别 & 算法 KeyPairGeneratorSpec API 18-22 仅非对称 KeyGenParameterSpec API 23+ 支持对称/非对称 配置密钥参数 用途、填充、密钥大小 生成密钥/密钥对 KeyPairGenerator / KeyGenerator 密钥持久化到KeyStore 📌 关键要点 1. 别名唯一且持久 2. 私钥不出 KeyStore 3. API ≥23 优先新 Spec
警告:千万别在生产环境中用 KeyPairGeneratorSpec 生成对称密钥——它根本不支持。我见过有人把 AES 密钥的字节数组硬编码到代码里,这是最危险的做法之一。一旦 APK 被逆向,密钥等于拱手送人。永远使用 AndroidKeyStore 来保管密钥材料。

嗯,到这里,RSA 密钥对和 AES 密钥如何通过 AndroidKeyStore 安全生成,你应该已经清楚了。下次写代码时,记得先确认一下 targetSdkVersion,该用 KeyGenParameterSpec 就别偷懒用老 API。你的密钥安全,就从这一步开始。