4. 非对称加密保护存储密钥:RSA/OAEP 与硬件隔离
对称加密(AES)需要密钥持久化,而密钥本身若直接明文存储将功亏一篑。本章引入非对称加密壳:使用 RSA+OAEP 包装 AES 密钥,并将私钥锚定在硬件隔离区(TEE/SE),实现“密钥加密密钥”的安全闭环。Android KeyStore 和 iOS Secure Enclave 是这一架构的典型平台实现。
4.1 为什么需要非对称密钥包装
AES 密钥一旦泄露,所有加密数据等于明文。直接存储 AES 密钥违背零信任原则。非对称加密提供一种“公钥加密、私钥解密”的不对称能力:
- 公钥(Public Key)——用于加密 AES 密钥,可公开存储或嵌入代码。
- 私钥(Private Key)——用于解密 AES 密钥,必须隔离在硬件安全区域,应用无法直接读取其明文。
设计原则:非对称加密只保护小数据量(如 AES 密钥),不直接加密业务数据。业务数据由 AES-GCM 加密,AES 密钥再由 RSA-OAEP 包装,形成“数据加密密钥 → 密钥加密密钥”两层结构。
4.2 RSA-OAEP 加密原理
OAEP(Optimal Asymmetric Encryption Padding)是 RSA 加密的标准填充方案,它引入随机盐值和哈希校验,能抵抗选择密文攻击(CCA)。核心流程:
- 生成 RSA-2048/4096 密钥对(私钥不进应用内存)。
- 对 AES 密钥(32 字节)应用 OAEP 填充,得到固定长度块(RSA-2048 下为 256 字节)。
- 用公钥执行 RSA 模幂运算,输出密文。
- 解密时,私钥执行模幂逆运算,去除 OAEP 填充,还原 AES 密钥。
| 参数项 | 推荐值 | 备注 |
|---|---|---|
| RSA 密钥长度 | 2048 / 4096 位 | 2048 满足当前安全基线,4096 更高强度 |
| OAEP 哈希算法 | SHA-256 | 避免 SHA-1 碰撞风险 |
| OAEP 盐值长度 | 32 字节 | 与哈希输出长度一致 |
| 最大加密数据量 | 190 字节(2048 位) | OAEP 开销约 66 字节,AES-256 密钥完全满足 |
安全警告:禁止使用 RSA 无填充(PKCS#1 v1.5)或 ECB 模式。必须使用 OAEP 且指定 SHA-256 及以上哈希。旧版 PKCS#1 v1.5 在 Bleichenbacher 攻击下极易被破解。
4.3 硬件隔离:TEE 与 SE 架构
私钥必须存放在应用进程无法触及的区域。移动端提供两级硬件隔离:
- TEE(Trusted Execution Environment):与 Rich OS(Android/iOS)并行运行的微型安全 OS,拥有独立内存和外设。私钥运算在 TEE 内完成,应用仅能调用结果。
- SE(Secure Element):独立安全芯片,拥有自己的 CPU、RAM 和闪存。私钥永远不离开芯片。iOS Secure Enclave 实际是集成在 SoC 内的 SE。
4.4 Android KeyStore 实现
Android KeyStore 自 Android 6.0(API 23)起支持硬件背书的 RSA 密钥对。密钥一旦生成,私钥的导出接口返回 null,运算在 TEE 内完成。
4.4.1 生成 RSA 密钥对并指定硬件隔离
// 仅在首次初始化时调用
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder(
"aes_wrapping_key",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setDigests(KeyProperties.DIGEST_SHA256)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setKeySize(2048)
// 要求硬件隔离(TEE 或 StrongBox)
.setIsStrongBoxBacked(true) // Android 9+ 专用安全芯片
.build());
kpg.generateKeyPair();
4.4.2 使用公钥加密 AES 密钥
// 加载密钥对
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
PublicKey pub = ks.getCertificate("aes_wrapping_key").getPublicKey();
// RSA-OAEP 加密
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pub);
byte[] aesKey = /* 32 字节 AES 密钥 */;
byte[] encryptedKey = cipher.doFinal(aesKey);
// 将 encryptedKey 持久化到 SharedPreferences 或数据库
4.4.3 在 TEE 内解密 AES 密钥
PrivateKey priv = (PrivateKey) ks.getKey("aes_wrapping_key", null);
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, priv);
byte[] decryptedKey = cipher.doFinal(encryptedKey);
// decryptedKey 仅存于堆内存,使用后立即置零
StrongBox 回退处理:部分低端设备不支持
setIsStrongBoxBacked(true)。建议先尝试此设置,捕获 StrongBoxUnavailableException 后降级为标准 TEE(仍比纯软件安全)。高安全场景可拒绝在此类设备上运行。
4.5 iOS Secure Enclave 实现
iOS 的 Secure Enclave(SEP)是硬件安全协处理器。私钥生成后直接写入 SE 内部存储,应用只能通过 SecKeyRef 句柄引用,无法获取私钥数据。iOS 10+ 支持 kSecAttrTokenIDSecureEnclave 属性。
4.5.1 生成硬件绑定的 RSA 密钥对
// Swift 示例
let accessControl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
.privateKeyUsage,
nil)!
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: [
kSecAttrAccessControl as String: accessControl,
kSecAttrIsPermanent as String: true
]
]
var error: Unmanaged<CFError>?
let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)
let publicKey = SecKeyCopyPublicKey(privateKey!)
4.5.2 OAEP 加密与解密
// 加密(在应用层执行)
let plainData = aesKeyData as CFData
let algorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
guard let cipherData = SecKeyCreateEncryptedData(
publicKey, algorithm, plainData, &error) else {
// 处理错误
return
}
// 解密(在 Secure Enclave 内执行)
guard let decryptedData = SecKeyCreateDecryptedData(
privateKey, algorithm, cipherData, &error) else {
// 处理错误
return
}
// decryptedData 即为 AES 密钥
iOS 注意:Secure Enclave 不支持
kSecAttrTokenIDSecureEnclave 配置时使用 kSecAttrKeyTypeEC(ECIES)。若必须使用 RSA,需在 iOS 设备上确认 SEP 固件版本是否支持 RSA 运算(iPhone 6s 及更新设备均支持)。对于不支持 RSA 的旧设备,回退至软件 RSA 并锁入 Keychain 数据保护类 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly。
4.6 调试与验证清单
| # | 检查项 | 验证方法 | 预期结果 |
|---|---|---|---|
| 1 | 私钥是否可导出 | 调用 getEncoded() 或 SecKeyCopyExternalRepresentation |
返回 null / 失败 |
| 2 | OAEP 填充是否正确 | 用已知 AES 密钥加密,再解密比对 | 完全一致 |
| 3 | 硬件隔离是否启用 | Android: KeyInfo.isInsideSecureHardware() |
返回 true |
| 4 | 密钥持久化加密 | 查看数据库或 SharedPreferences 存储值 | 256 字节密文,不可识别 |
| 5 | OTA 升级后密钥可用 | 重启应用后执行解密 | 解密成功 |
Android KeyInfo 查询示例:
PrivateKey priv = (PrivateKey) ks.getKey("aes_wrapping_key", null);
if (priv instanceof KeyInfo) {
KeyInfo info = (KeyInfo) priv;
boolean isHardware = info.isInsideSecureHardware();
boolean isStrongBox = info.isStrongBoxBacked();
Log.d("KEY_INFO", "Hardware: " + isHardware + " StrongBox: " + isStrongBox);
}
4.7 常见错误与稳定性优化
- OAEP 参数不匹配:加密与解密必须使用完全相同的哈希和 MGF1 摘要算法。Android 默认 MGF1 与 OAEP 哈希一致,但仍建议显式指定
OAEPWithSHA-256AndMGF1Padding。 - RSA 密钥大小超过硬件限制:部分 TEE 只支持 2048 位,4096 位可能导致
KeyStoreException。建议优先使用 2048 位,除非确认硬件兼容。 - Secure Enclave 锁定:连续多次错误解锁(如生物认证失败)会导致 SE 暂时不可用。应用需捕获
errSecAuthFailed并提示用户稍后重试。 - 密钥被销毁后无法恢复:Android KeyStore 执行
reset或应用卸载会删除硬件密钥。必须提前将 RSA 公钥对应的加密密文导出到云端备份,或使用 KeyStore 的KeyChain备份机制。 - 性能优化:RSA-OAEP 单次加密约 5–15ms(取决于硬件)。解密在 TEE/SE 内因上下文切换需 20–50ms。建议在应用启动时缓存解密后的 AES 密钥(仅内存,不落盘),后续业务数据直接走 AES-GCM 快速解密。
// 内存缓存 AES 密钥示例(Android)
class AESKeyCache {
private byte[] cachedKey = null;
private final Object lock = new Object();
public byte[] getKey(Context ctx) {
synchronized (lock) {
if (cachedKey != null) return cachedKey;
// 从 KeyStore 解密
cachedKey = decryptAesKeyFromKeystore(ctx);
return cachedKey;
}
}
public void clear() {
synchronized (lock) {
if (cachedKey != null) {
Arrays.fill(cachedKey, (byte) 0);
cachedKey = null;
}
}
}
}
内存安全实践:AES 密钥使用完毕后立即调用
Arrays.fill() 或 memset_s 清零,避免被内存 dump 或堆分析工具捕获。Swift 中可使用 Data 的 resetBytes(in:) 方法。
4.8 部署建议
| 维度 | Android KeyStore | iOS Secure Enclave |
|---|---|---|
| 最低 API 版本 | Android 6.0 (API 23) | iOS 10.0 |
| 硬件隔离类型 | TEE(TrustZone)/ StrongBox | Secure Enclave (SEP) |
| 密钥材料导出 | getEncoded() 返回 null |
SecKeyCopyExternalRepresentation 返回空 |
| 备份场景 | 卸载即销毁,需云备份密文 | 备份到 iCloud Keychain 需特殊 entitlement |
| RSA 密钥大小 | 2048 / 4096 位(部分 TEE 仅 2048) | 2048 位(SEP 限制) |
总结:RSA-OAEP 结合硬件隔离为移动端 AES 密钥提供“一次封装、硬件解密”的强安全闭环。Android 优先使用 StrongBox,iOS 优先使用 Secure Enclave。在密钥持久化、性能、备份三个维度做平衡设计,即可在生产环境中有效抵御冷启动攻击和运行时注入。