3. 密钥对的生成与安全存储:使用OpenSSL生成RSA/SM2密钥、密钥存储到Android Keystore/iOS Keychain

3.1 原理概述

支付SDK在进行数据签名、验签和密钥协商时,需要一套可靠的密钥对。密钥对的生成与安全存储是整个安全体系的根基。本章聚焦两种主流非对称算法:RSA(国际通用,2048/4096位)和SM2(中国国家密码标准,基于椭圆曲线)。安全存储利用移动端硬件隔离能力:Android Keystore(基于TEE/StrongBox)和iOS Keychain(基于Secure Enclave),防止私钥被应用进程或恶意软件读取。

3.2 关键概念对比

维度RSASM2
算法基础大整数分解椭圆曲线离散对数
推荐密钥长度2048 / 4096 bit256 bit(曲线参数固定)
签名性能较慢(指数运算)快(标量乘)
国密合规否是(GM/T 0003-2012)
Android Keystore支持是(API 18+)是(API 28+,需系统支持SM2)
iOS Keychain支持是(iOS 10+)是(需CryptoKit或第三方实现)

3.3 实现流程

  1. 选择算法与密钥长度 — 支付场景建议RSA-4096或SM2-256。
  2. 使用OpenSSL命令行生成密钥对 — 开发阶段生成测试密钥;生产环境需在安全设备中生成。
  3. 解析密钥文件 — 提取公钥(.pem/.der)和私钥(PKCS#8/EC私有密钥格式)。
  4. 集成到移动端 — 将私钥导入Android Keystore / iOS Keychain;公钥用于传输。
  5. 验证存储完整性 — 签名一个随机数并验签,确保硬件安全模块正确工作。
安全建议:私钥永远不应离开安全存储区。生成环节应在专用离线机器或HSM中进行,导出公钥后立即销毁临时私钥副本。

3.4 密钥对生成命令(OpenSSL)

RSA密钥对生成

# 生成私钥(PKCS#8格式,无加密便于导入Keystore;生产环境建议加aes256)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out private_rsa.pem

# 提取公钥
openssl pkey -in private_rsa.pem -pubout -out public_rsa.pem

# 查看私钥详细信息
openssl pkey -in private_rsa.pem -text -noout

SM2密钥对生成

# 生成SM2私钥(使用EC参数sm2p256v1)
openssl ecparam -genkey -name SM2 -out private_sm2.pem

# 提取公钥
openssl ec -in private_sm2.pem -pubout -out public_sm2.pem

# 验证曲线
openssl ec -in private_sm2.pem -text -noout | head -5
注意:OpenSSL 1.1.1+原生支持SM2;如果使用旧版本(1.0.2),需要编译时开启SM2支持或使用国密分支(如GmSSL)。SM2私钥文件格式为EC私钥,Android Keystore导入时需要转换为PKCS#8。

3.5 Android Keystore存储实现

API 级别要求

  • RSA Key Pair:KeyPairGeneratorSpec (API 18+),推荐使用KeyGenParameterSpec (API 23+)。
  • SM2 Key Pair:API 28+ 可使用KeyGenParameterSpec.Builder指定算法"EC",同时设置ECCurve为"secp256r1"(SM2曲线等价于secp256r1的变体,但Android原生SM2支持有限;建议使用Bouncy Castle或GMSSL接口)。

Android RSA密钥导入示例

// 假设已有PEM格式私钥字符串
String privateKeyPem = "-----BEGIN PRIVATE KEY-----\n...";

// 加载PKCS8私钥
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
    Base64.decode(privateKeyPem.replace("-----BEGIN PRIVATE KEY-----", "")
                               .replace("-----END PRIVATE KEY-----", "")
                               .replaceAll("\\s", ""), Base64.DEFAULT));
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(keySpec);

// 存储到Android Keystore
String alias = "payment_signing_key_rsa";
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.setEntry(alias, new KeyStore.PrivateKeyEntry(privateKey, null),
                  new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
                      .setDigests(KeyProperties.DIGEST_SHA256)
                      .build());

Android SM2密钥生成示例(原生KeyStore)

KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder("payment_sm2_key",
        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
        .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) // 与SM2曲线同阶
        .setDigests(KeyProperties.DIGEST_SM3) // Android 13+ 支持SM3
        .build();
kpg.initialize(spec);
KeyPair keyPair = kpg.generateKeyPair();

3.6 iOS Keychain存储实现

密钥格式转换(DER / PEM)

iOS Keychain要求私钥为SecKey对象,通常从PKCS#12或DER格式导入。推荐先使用OpenSSL将PEM转为DER:

# RSA私钥DER
openssl pkcs8 -topk8 -inform PEM -outform DER -in private_rsa.pem -nocrypt -out private_rsa.der

# SM2私钥DER(EC私钥)
openssl ec -inform PEM -outform DER -in private_sm2.pem -out private_sm2.der

iOS导入RSA私钥到Keychain

// 加载DER数据
NSData *keyData = [NSData dataWithContentsOfFile:@"private_rsa.der"];
NSDictionary *options = @{
    (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
    (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
    (__bridge id)kSecAttrKeySizeInBits: @4096
};
CFErrorRef error = NULL;
SecKeyRef privateKey = SecKeyCreateWithData((__bridge CFDataRef)keyData,
                                             (__bridge CFDictionaryRef)options,
                                             &error);

// 存储到Keychain
NSDictionary *addDict = @{
    (__bridge id)kSecClass: (__bridge id)kSecClassKey,
    (__bridge id)kSecAttrLabel: @"com.payment.signing.rsa",
    (__bridge id)kSecValueRef: (__bridge id)privateKey,
    (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
};
SecItemAdd((__bridge CFDictionaryRef)addDict, NULL);

3.7 流程结构图(SVG)

密钥对生成与安全存储流程 选择算法(RSA / SM2) OpenSSL生成密钥对 Android Keystore iOS Keychain KeyGenParameterSpec TEE / StrongBox SecKeyCreateWithData Secure Enclave 验签验证存储正确 部署完成 私钥永不离开硬件安全区

3.8 调试与验证

Android侧验签测试

// 使用Keystore中的私钥签名
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey privateKey = (PrivateKey) keyStore.getKey("payment_signing_key_rsa", null);
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update("test_data".getBytes());
byte[] signature = signer.sign();

// 使用公钥验签
PublicKey publicKey = keyStore.getCertificate("payment_signing_key_rsa").getPublicKey();
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(publicKey);
verifier.update("test_data".getBytes());
boolean valid = verifier.verify(signature); // true 表示存储正确

iOS侧验签测试

// 签名
SecKeyRef privateKey = ...; // 从Keychain取回
NSData *data = [@"test_data" dataUsingEncoding:NSUTF8StringEncoding];
CFErrorRef error = NULL;
NSData *sig = (NSData *)CFBridgingRelease(SecKeyCreateSignature(privateKey,
                         kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256,
                         (__bridge CFDataRef)data,
                         &error));

// 验签(需公钥)
SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
Boolean ok = SecKeyVerifySignature(publicKey,
                kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256,
                (__bridge CFDataRef)data,
                (__bridge CFDataRef)sig,
                &error);

3.9 常见错误与解决方案

错误现象可能原因解决方法
java.security.InvalidKeyException私钥格式不满足PKCS#8要求使用openssl pkcs8 -topk8转换
SecItemAdd 返回 errSecMissingEntitlementKeychain访问权限配置错误在 Capabilities 中开启 Keychain Sharing
SM2验签失败曲线参数不一致(使用secp256r1代替SM2)确认Android设备原生支持SM2(Pixel 6+),否则集成Bouncy Castle
KeyStore未找到别名未正确调用setEntry或别名冲突使用keyStore.containsAlias()检查,删除旧Key

3.10 性能与稳定性优化

  • 密钥生成频率:支付SDK应仅在首次启动或远程轮换指令触发时生成密钥对,避免在UI线程中执行。建议使用AsyncTask或协程在后台线程生成。
  • 缓存公钥:公钥可从Keychain/Keystore中导出并缓存到内存(WeakReference或LruCache),减少每次签名前的证书链加载。
  • Keystore初始化懒加载:不要在应用启动时加载所有密钥,按需获取(keyStore.getEntry),避免首次解锁延迟。
  • iOS Keychain性能:SecItemCopyMatching查询应使用精确的kSecAttrLabel和kSecClass,避免遍历所有Keychain项。
  • 硬件隔离降级策略:当设备不支持TEE或Secure Enclave(如低端Android 6.0设备),应回退到软件Keystore(BKS),并用BIO加密存储私钥(但安全性降低,需交易风险)。
生产环境基线:所有支付关键私钥必须存储在硬件隔离区(Android StrongBox / iOS Secure Enclave),不允许使用纯软件方案存储签名私钥。公钥可明文存储,但需通过证书链校验防止替换攻击。