4. 非对称加密保护存储密钥

对称加密(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)。核心流程:

  1. 生成 RSA-2048/4096 密钥对(私钥不进应用内存)。
  2. 对 AES 密钥(32 字节)应用 OAEP 填充,得到固定长度块(RSA-2048 下为 256 字节)。
  3. 用公钥执行 RSA 模幂运算,输出密文。
  4. 解密时,私钥执行模幂逆运算,去除 OAEP 填充,还原 AES 密钥。
RSA-OAEP 参数推荐
参数项 推荐值 备注
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。
RSA-OAEP 包装 AES 密钥 + 硬件隔离流程 应用层(Rich OS) AES-GCM 加密数据 AES 密钥(32B) RSA-OAEP 加密包装 硬件隔离区(TEE / SE) RSA 私钥(2048位) OAEP 逆填充 返回 AES 密钥(仅内存) 安全闭环说明 ① 应用生成 AES 密钥,并获取 RSA 公钥 ② 使用 RSA-OAEP 加密 AES 密钥 ③ 密文持久化到本地文件 / 数据库 ④ 读取时,将密文传入 TEE/SE ⑤ TEE/SE 用私钥解密,返回 AES 明文 ⑥ 应用用 AES 密钥解密业务数据 ⑦ 业务使用完毕,立即擦除 AES 密钥 威胁防范 ✓ 私钥永不离开硬件隔离区 ✓ 即使应用被 HOOK,也无法 dump 私钥 ✓ OAEP 抗 CCA 攻击 ✓ AES 密钥只在内存临时存在 RSA 公钥 密文 256B

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。在密钥持久化、性能、备份三个维度做平衡设计,即可在生产环境中有效抵御冷启动攻击和运行时注入。