第3章:Keychain写入加密数据(对称加密方案)

本章聚焦于对称加密方案,使用 AES-256-GCM 算法加密蓝牙通信数据,并将密钥安全写入 Keychain。内容包括密钥生成方式(SecKeyGeneratePair 与非对称随机密钥)、访问控制配置(kSecAttrAccessControl)以及完整实现流程。

3.1 原理说明

对称加密使用同一密钥进行加密与解密。在蓝牙数据传输场景中,收发双方需持有相同密钥。Keychain 作为 iOS 安全存储容器,可保护密钥免受应用沙箱外未经授权的访问。AES-256-GCM 提供认证加密,同时保证机密性与完整性。

密钥生成策略有两种:

  • 随机密钥:使用 SecRandomCopyBytes 生成 256 位随机数据,适合单次会话或临时密钥。
  • SecKeyGeneratePair:通常用于非对称密钥对,但也可生成对称密钥(通过指定 kSecAttrKeyType 为 kSecAttrKeyTypeAES),并直接写入 Keychain。

存储时需配置 kSecAttrAccessControl 以设定访问条件(如生物认证、设备密码)。

3.2 实现流程

  1. 生成 32 字节随机密钥(AES‑256)。
  2. 创建 Keychain 查询字典,指定 kSecClass = kSecClassKey、kSecAttrKeyType = kSecAttrKeyTypeAES、kSecAttrKeySizeInBits = 256。
  3. 设置 kSecAttrAccessControl,例如使用 SecAccessControlCreateWithFlags 添加 kSecAccessControlUserPresence。
  4. 使用 SecItemAdd 将密钥数据写入 Keychain。
  5. 验证写入结果(errSecSuccess 或错误码)。
提示:若应用需频繁读取密钥,可考虑在第一次写入后使用 SecItemCopyMatching 并用 kSecUseDataProtectionKeychain 优化性能。

3.3 代码/配置/API 调用

以下示例展示生成随机密钥并写入 Keychain:

// 生成 32 字节随机密钥
uint8_t keyBytes[32];
SecRandomCopyBytes(kSecRandomDefault, 32, keyBytes);
NSData *aesKeyData = [NSData dataWithBytes:keyBytes length:32];

// 创建访问控制
SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(
    kCFAllocatorDefault,
    kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
    kSecAccessControlUserPresence,
    NULL);
if (!accessControl) {
    // 处理创建失败
    return;
}

// 构建 Keychain 查询字典
NSDictionary *addQuery = @{
    (__bridge id)kSecClass: (__bridge id)kSecClassKey,
    (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeAES,
    (__bridge id)kSecAttrKeySizeInBits: @256,
    (__bridge id)kSecAttrAccessControl: (__bridge id)accessControl,
    (__bridge id)kSecValueData: aesKeyData,
    (__bridge id)kSecAttrLabel: @"com.yourApp.bluetooth.aes256key",
};

// 写入 Keychain
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
if (status == errSecSuccess) {
    NSLog(@"密钥写入成功");
} else {
    NSLog(@"写入失败,错误码: %d", (int)status);
}
CFRelease(accessControl);

若使用 SecKeyGeneratePair 生成对称密钥,需配置 kSecAttrKeyType = kSecAttrKeyTypeAES,并指定 kSecPrivateKeyAttrs 或 kSecPublicKeyAttrs(对称密钥通常只存储为私钥项)。

3.4 调试与常见错误

错误码说明常见原因
errSecDuplicateItem已存在相同标签的密钥kSecAttrLabel 重复;可先使用 SecItemDelete 删除旧项
errSecParam参数错误访问控制对象为 NULL 或 kSecAttrKeySizeInBits 不匹配
errSecAuthFailed用户认证失败生物识别或密码验证未通过;设备未设置密码时使用 kSecAccessControlUserPresence
errSecNotAvailableKeychain 不可用应用未启用 Keychain 功能(Capabilities 中未添加 Keychain Sharing)
警告:kSecAttrAccessControl 一旦设置后无法修改。若需更新访问条件,必须删除旧项并重新写入。另外,在模拟器上测试生物识别相关访问控制可能无法触发,建议真机调试。

3.5 性能与稳定性优化

  • 重用密钥:避免每次蓝牙连接都重新生成密钥。建立一次性密钥后,使用唯一标识符(如设备 UUID)关联存储。
  • 合理设置访问级别:对非敏感数据使用 kSecAttrAccessibleWhenUnlockedThisDeviceOnly 以减少 Keychain 访问延迟。
  • 错误重试机制:SecItemAdd 可能因系统锁竞争偶尔返回 errSecInteractionNotAllowed;可添加短暂重试逻辑(间隔 100ms)。
  • 批量操作:若写入大量密钥,使用事务(Keychain 不支持原生事务,可通过串行队列串行化操作)避免并发冲突。
AES-256-GCM 密钥生成与保存 Keychain 流程 生成 32 字节随机密钥 创建 SecAccessControl 构建 Keychain 字典 SecItemAdd 写入 检查 errSecSuccess 密钥可用 错误处理/重试 注意:相同的 kSecAttrLabel 会被视为重复项,写入前需清理旧数据

3.6 本节要点回顾

要点说明
密钥生成使用 SecRandomCopyBytes 或 SecKeyGeneratePair(设置 kSecAttrKeyTypeAES)
访问控制kSecAttrAccessControl 控制谁可以读取密钥,支持生物识别/设备密码
存储标签kSecAttrLabel 用于唯一标识密钥项,避免冲突
错误处理常见错误码如 errSecDuplicateItem、errSecParam、errSecAuthFailed
优化建议重用密钥,适当设置访问级别,添加重试逻辑