第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+)
keySizesetKeySize(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字节)。实际蓝牙数据传输需混合加密:

  1. 随机生成AES-256密钥(对称密钥)
  2. 用AES加密蓝牙数据(支持任意长度)
  3. 用RSA公钥加密AES密钥
  4. 将加密后的AES密钥和密文一起发送
  5. 接收方用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 本章核心流程图

RSA+AES混合加密流程(Keystore组件参与) 生成RSA密钥对 Keystore存储私钥 导出RSA公钥 生成随机AES-256密钥 AES-GCM加密蓝牙数据 RSA-OAEP加密AES密钥 组装[加密密钥+IV+密文+Tag]

4.7 部署与最佳实践

  • 密钥有效期:生产环境建议设置5-10年,无需使用无限期。可预留setEndDate()以便后续轮换。
  • 别名管理:在SharedPreferences中持久化别名索引,避免硬编码字符串。
  • 异常处理:KeyStoreException常见于别名重复、证书算法不匹配。建议在生成前调用keyStore.containsAlias(alias)检查。
  • 性能优化:RSA密钥对生成在硬件安全环境中可能耗时1-3秒,建议在子线程中执行,并显示进度。
常见错误:使用KeyPairGeneratorSpec时忘记设置setSubject()会导致KeyStoreException;使用KeyGenParameterSpec时未设置setBlockModes和setEncryptionPaddings会导致InvalidAlgorithmParameterException。请严格参考表4-2进行配置。