🔐 加密方案说明

三长两短 - 三层加密架构详解

Scheme B: 分离验证加密方案

📋 方案概述

本系统采用三层AES-GCM-256加密架构,实现了数据的分层保护和灵活访问控制。

核心特性:
  • ✅ App端仅可解密姓名用于验证,无法获取完整用户信息
  • ✅ 完整用户信息需在安全环境下使用外部密钥解密
  • ✅ 动态二维码和分享码,每5分钟自动更新
  • ✅ 时间敏感的密钥设计,过期自动失效
  • ✅ UMID(独立映射ID)防止直接关联用户信息

🏗️ 三层加密架构

🔵 Layer 1: 用户数据加密

目的:保护用户完整信息

密钥:User Key(用户主密钥,256位)

算法:AES-GCM-256

输入:完整用户信息(姓名、身份证、电话等所有字段)

输出:Layer1Data(加密后的用户数据)

// Swift示例
let userData = UserData(
    name: "张三",
    idCard: "110101199001011234",
    phone: "13800138000",
    // ... 其他字段
)

let userKey = generateUserKey() // 256位密钥
let layer1Data = encryptAESGCM(userData, key: userKey)
🟢 Layer 2: UMID生成

目的:生成唯一用户映射ID,防止直接关联

算法:SHA256哈希

输入:Layer1Data

输出:UMID(64位十六进制字符串)

// Swift示例
let umid = SHA256.hash(data: layer1Data)
    .compactMap { String(format: "%02x", $0) }
    .joined()
// 结果: "a1b2c3d4e5f6..." (64位)
💡 UMID特性:
  • 同一用户数据永远生成相同UMID
  • 无法从UMID反推用户信息
  • 用于CloudKit和服务器中作为唯一标识
🟣 Layer 3: 分享加密

目的:生成二维码/分享码,包含姓名和UMID

密钥:App Key(时间敏感,每5分钟更新)

算法:AES-GCM-256

输入:Layer3Info(姓名 + UMID + 时间戳等)

输出:Layer3Data → 二维码/分享码

// Swift示例
let layer3Info = Layer3Info(
    name: "张三",
    umid: umid,
    generatedAt: Date(),
    validityMinutes: 5,
    nonce: UUID().uuidString
)

let appKey = generateAppKey(timeWindow: currentTimeWindow)
let layer3Data = encryptAESGCM(layer3Info, key: appKey)

// 生成二维码和分享码
let qrCode = generateQRCode(layer3Data)
let shareCode = generateShareCode(layer3Data) // 6位

🔄 数据流程

📤 生成流程(A用户)

  1. 输入用户信息 → 姓名、身份证等
  2. Layer 1加密 → 使用User Key加密 → Layer1Data
  3. 生成UMID → SHA256(Layer1Data) → UMID
  4. Layer 3加密 → 使用App Key加密(姓名 + UMID) → Layer3Data
  5. 生成分享 → Layer3Data → 二维码 + 分享码
  6. 上传CloudKit → 保存 Layer1Data, Layer3Data, UMID

📥 验证流程(B用户扫码/输入分享码)

  1. 扫描/输入 → 获取Layer3Data
  2. 检查时效 → 计算当前时间窗口的App Key
  3. Layer 3解密 → 使用App Key解密 → 获取姓名和UMID
  4. 姓名验证 → 显示姓名首字,要求输入最后一字
  5. 验证通过 → 可以发起状态请求
  6. 获取完整信息 → ❌ App端无法解密(需要外部安全环境 + User Key)

🔑 密钥管理

密钥类型 用途 生成方式 有效期 存储位置
User Key Layer 1加密(用户数据) 用户设置时生成 永久 Keychain(安全)
App Key Layer 3加密(分享) 基于时间窗口生成 5分钟 运行时计算
External Key 完整数据解密 外部安全密钥 - 物理设备/法律机构

⏰ App Key时间窗口机制

// 时间窗口:5分钟
let timeWindow = Int(Date().timeIntervalSince1970) / 300

// App Key派生
func generateAppKey(timeWindow: Int) -> SymmetricKey {
    let baseKey = getBaseAppKey() // 应用基础密钥
    let material = baseKey + String(timeWindow)
    return deriveKey(from: material)
}

// 验证时尝试当前和上一个时间窗口
func decryptWithTimeWindow(_ data: Data) -> Layer3Info? {
    let currentWindow = getCurrentTimeWindow()
    
    // 尝试当前窗口
    if let info = tryDecrypt(data, window: currentWindow) {
        return info
    }
    
    // 尝试上一个窗口(容错)
    if let info = tryDecrypt(data, window: currentWindow - 1) {
        return info
    }
    
    return nil // 过期
}

🛡️ 安全特性

1. 分层访问控制

  • App端:只能解密Layer 3,获取姓名首字和UMID
  • 请求方:必须验证姓名最后一字才能发起请求
  • 完整数据:需要外部环境 + User Key才能解密Layer 1

2. 时间敏感设计

  • 二维码和分享码每5分钟自动失效
  • 过期的分享无法解密,即使获取到数据也无效
  • 自动防止旧二维码/分享码泄露风险

3. 双重验证机制

  • 时间验证:App Key时间窗口检查
  • 姓名验证:必须正确输入姓名最后一字
  • 两层验证都通过才能发起状态请求

4. 数据隔离

⚠️ 关键限制:
  • CloudKit只存储加密数据(Layer1Data, Layer3Data)
  • 服务器只存储UMID和分享码,无任何明文信息
  • User Key永不上传,永不离开用户设备
  • External Key由法律机构或指定人员持有

💡 使用场景

场景1:日常分享(验证姓名)

A生成二维码B扫描App解密姓名B验证最后一字发起状态请求

✅ App端完成,无需额外密钥

场景2:身故后解密(获取完整信息)

A身故指定人员持有External Key从CloudKit获取Layer1Data使用User Key + External Key解密获取完整用户信息

⚠️ 需要在安全环境下操作,不在App内进行

场景3:分享码碰撞处理

生成分享码检查服务器是否已存在存在则重新生成记录碰撞用户量增长时自动升级位数

📊 6位 → 8位 → 10位 → 12位自动升级

🔧 技术实现

加密算法:AES-GCM-256

import CryptoKit

func encryptAESGCM(_ data: T, key: SymmetricKey) throws -> Data {
    let jsonData = try JSONEncoder().encode(data)
    let sealedBox = try AES.GCM.seal(jsonData, using: key)
    return sealedBox.combined!
}

func decryptAESGCM(_ encryptedData: Data, key: SymmetricKey) throws -> T {
    let sealedBox = try AES.GCM.SealedBox(combined: encryptedData)
    let decryptedData = try AES.GCM.open(sealedBox, using: key)
    return try JSONDecoder().decode(T.self, from: decryptedData)
}

UMID生成:SHA256

import CryptoKit

func generateUMID(from layer1Data: Data) -> String {
    let hash = SHA256.hash(data: layer1Data)
    return hash.compactMap { String(format: "%02x", $0) }.joined()
}

分享码生成:动态长度

func generateShareCode(length: Int = 6) -> String {
    let chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // 去除易混淆字符
    return String((0..<length).map { _ in chars.randomElement()! })
}
← 返回首页