第4章 密钥分层体系:主密钥(KEK)、工作密钥(DEK)、密钥派生函数与轮换策略

4.1 组件边界

密钥分层组件位于应用层与底层安全硬件之间,负责定义和管理密钥的生成、派生、加密存储、使用与轮换。其核心边界如下:

  • 上层接口:通过密钥 ID(UUID)对外提供加密/解密操作,不暴露裸密钥材料。
  • 下层依赖:Android KeyStore / TEE / StrongBox 提供根信任锚点(Root of Trust)。
  • 数据边界:仅接触加密后的密钥材料(KEK 加密 DEK)和派生中间数据,不读取用户明文数据。
  • 生命周期边界:管理密钥从创建、激活、冻结到销毁的完整状态机。

4.2 系统分层

层级角色存储位置示例
密钥存储层根信任锚点Android KeyStore / TEE主密钥 KEK(硬件绑定时不可导出)
密钥管理层派生、加密、缓存 DEK应用私有文件 / EncryptedSharedPreferencesDEK 被 KEK 加密后持久化
密码学使用层执行加解密应用堆内存(零化策略)使用内存中的 DEK 调用 AES-GCM

4.3 数据流

  1. 加密流程:应用请求加密数据 → 密钥管理组件通过 ID 查找被 KEK 加密的 DEK → 用 TEE 内的 KEK 解密 DEK → 使用 DEK 在内存中执行 AES-GCM 加密 → 返回密文并零化 DEK。
  2. 解密流程:同上逆向,需验证 GCM 认证标签。
  3. 派生流程:用户输入口令 → 调用 scrypt/PBKDF2 生成 KEK → 存储 KEK 至 KeyStore 或直接用作根密钥。
  4. 轮换流程:标记旧 DEK 为“过期” → 为新数据使用新 DEK → 后台任务用新 DEK 重新加密旧密文 → 删除旧 DEK 密文。

4.4 信任链路

信任边界从 Boot ROM → TEE → Android KeyStore → 密钥管理库 → 应用进程。每一层通过签名验证和隔离机制递进信任,其中 KEK 由硬件安全模块保护,DEK 仅以加密形态存在于应用文件系统中。用户口令派生时,派生函数在应用空间执行,结果受 OS 内存隔离保护。

4.5 密钥派生函数选型权衡

属性PBKDF2scrypt
迭代轮数可配置(推荐 100k+)可配置
内存消耗低(固定)高(可配置,推荐 64MiB)
GPU/ASIC 抗性较弱较强(内存硬)
Android 兼容性普遍支持(API 1+)需 API 26+ 或引入 Bouncy Castle
推荐场景低算力设备、快速原型高安全要求、用户口令派生 KEK
建议:对于用户口令派生,优先使用 scrypt(内存成本 ≥ 64MiB,CPU 成本 ≥ 2^14);若需兼容低版本,退而使用 PBKDF2 且迭代次数 ≥ 600k。

4.6 风险点

  • KEK 泄露:所有 DEK 均可被解密,导致数据批量暴露。需依赖 KeyStore 硬件隔离。
  • 派生函数参数弱化:迭代次数过低、内存成本过小,可被暴力破解。
  • 轮换期间数据不一致:若未保留旧 DEK 直至所有密文完成重加密,会导致数据不可解密。
  • 密钥存储未加密:DEK 若直接以明文写入 SharedPreferences,则任意有读权限的进程均可窃取。

4.7 落地步骤

  1. 初始化根密钥
    使用 Android KeyStore 生成 256 位 AES 密钥作为 KEK,且设置 KeyGenParameterSpec.Builder.setIsStrongBoxBacked(true) 并禁用导出。
  2. 实现 DEK 生成与加密存储
    每次为新数据集生成随机 AES-256-GCM 密钥作为 DEK,用 KEK 加密后存储到 JSON 文件或 EncryptedSharedPreferences。
  3. 集成密钥派生函数
    对于用户口令场景,使用 scrypt(可通过 javax.crypto.SecretKeyFactory 或第三方库)生成固定位数的 KEK,再注入 KeyStore 或直接作为加密密钥。
  4. 实现密钥轮换
    设计双缓冲策略:保留当前 DEK 和上一个版本的 DEK;轮换时生成新 DEK,标记旧 DEK 为“待回收”,所有写操作使用新 DEK,后台任务逐步重加密旧数据。
  5. 集成测试与回归
    验证:派生耗时 ≥ 200ms;轮换期间并发读写不丢数据;KEK 不可通过 KeyStore.getEntry 泄露;DEK 密文完整性标签校验通过。
// 示例:在 Android KeyStore 中生成不可导出的 KEK
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
        "master_key_v1",
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .setKeySize(256)
        .setIsStrongBoxBacked(true)
        .build();
KeyGenerator kg = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
kg.init(spec);
SecretKey kek = kg.generateKey();

4.8 验收标准

检查项预期结果
KEK 导出测试调用 KeyStore.getKey() 返回 null 或 UnrecoverableKeyException
DEK 加密存储完整性篡改 DEK 密文后解密失败(GCM 认证标签校验)
派生函数延迟scrypt (N=16384, r=8, p=1) 平均执行时间 ≥ 200ms
轮换并发正确性轮换过程中新数据使用新 DEK,旧数据仍可用旧 DEK 解密,重加密完成后旧 DEK 被安全删除
内存零化DEK 使用后显式调用 Arrays.fill(sek, (byte)0) 或使用 SecureZero
注意:密钥轮换时,必须保留旧 DEK 直到引用该 DEK 的所有密文都被重新加密或过期。建议使用“延迟删除”策略,在后台线程中逐步迁移。

4.9 密钥分层体系架构示意

密钥分层体系 & 信任流 用户口令 密钥派生函数 scrypt / PBKDF2 主密钥 KEK (硬件隔离不可导出) 工作密钥 DEK (随机生成,AES-256-GCM) 加密数据 (密文 + GCM 标签) TEE / StrongBox 信任根锚点 密钥轮换策略 双缓冲 / 延时删除 派生 加密保护 加解密操作 信任保护 触发轮换 信任链路:BootROM→TEE→KeyStore→App