第3章:Keychain写入加密数据(对称加密方案)
本章聚焦于对称加密方案,使用 AES-256-GCM 算法加密蓝牙通信数据,并将密钥安全写入 Keychain。内容包括密钥生成方式(SecKeyGeneratePair 与非对称随机密钥)、访问控制配置(kSecAttrAccessControl)以及完整实现流程。
3.1 原理说明
对称加密使用同一密钥进行加密与解密。在蓝牙数据传输场景中,收发双方需持有相同密钥。Keychain 作为 iOS 安全存储容器,可保护密钥免受应用沙箱外未经授权的访问。AES-256-GCM 提供认证加密,同时保证机密性与完整性。
密钥生成策略有两种:
- 随机密钥:使用
SecRandomCopyBytes生成 256 位随机数据,适合单次会话或临时密钥。 - SecKeyGeneratePair:通常用于非对称密钥对,但也可生成对称密钥(通过指定
kSecAttrKeyType为kSecAttrKeyTypeAES),并直接写入 Keychain。
存储时需配置 kSecAttrAccessControl 以设定访问条件(如生物认证、设备密码)。
3.2 实现流程
- 生成 32 字节随机密钥(AES‑256)。
- 创建 Keychain 查询字典,指定
kSecClass = kSecClassKey、kSecAttrKeyType = kSecAttrKeyTypeAES、kSecAttrKeySizeInBits = 256。 - 设置
kSecAttrAccessControl,例如使用SecAccessControlCreateWithFlags添加kSecAccessControlUserPresence。 - 使用
SecItemAdd将密钥数据写入 Keychain。 - 验证写入结果(
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 |
errSecNotAvailable | Keychain 不可用 | 应用未启用 Keychain 功能(Capabilities 中未添加 Keychain Sharing) |
警告:
kSecAttrAccessControl 一旦设置后无法修改。若需更新访问条件,必须删除旧项并重新写入。另外,在模拟器上测试生物识别相关访问控制可能无法触发,建议真机调试。
3.5 性能与稳定性优化
- 重用密钥:避免每次蓝牙连接都重新生成密钥。建立一次性密钥后,使用唯一标识符(如设备 UUID)关联存储。
- 合理设置访问级别:对非敏感数据使用
kSecAttrAccessibleWhenUnlockedThisDeviceOnly以减少 Keychain 访问延迟。 - 错误重试机制:
SecItemAdd可能因系统锁竞争偶尔返回errSecInteractionNotAllowed;可添加短暂重试逻辑(间隔 100ms)。 - 批量操作:若写入大量密钥,使用事务(Keychain 不支持原生事务,可通过串行队列串行化操作)避免并发冲突。
3.6 本节要点回顾
| 要点 | 说明 |
|---|---|
| 密钥生成 | 使用 SecRandomCopyBytes 或 SecKeyGeneratePair(设置 kSecAttrKeyTypeAES) |
| 访问控制 | kSecAttrAccessControl 控制谁可以读取密钥,支持生物识别/设备密码 |
| 存储标签 | kSecAttrLabel 用于唯一标识密钥项,避免冲突 |
| 错误处理 | 常见错误码如 errSecDuplicateItem、errSecParam、errSecAuthFailed |
| 优化建议 | 重用密钥,适当设置访问级别,添加重试逻辑 |