关于 axiom-keychain-ref
面向 iOS 和 macOS Keychain Services 开发者的 API 参考,详述 SecItem 操作、项目属性、唯一性规则、可访问性和返回值,帮助编写准确的查询与存储代码。
主要功能
- 01SecItem 增删改查函数行为
- 02Keychain 项目类别与属性
- 03各项目类别的唯一性规则
- 04搜索与返回属性参考
- 05可访问性和访问控制详情
适用场景
- 01编写 Keychain 增删改查查询
- 02依据唯一性规则诊断重复项目错误
- 03选择返回类型和可访问性设置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-keychain-ref安装后在 Claude Code 中输入/axiom-keychain-ref确认已加载。
详细介绍
Keychain Services API 参考
iOS/macOS Keychain Services 的综合 API 参考:SecItem CRUD 函数、项目类属性、唯一性约束、可访问性级别、访问控制标志、生物识别集成和错误代码。
快速参考
// Add a generic password
let addQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "[email protected]",
kSecValueData as String: "secret".data(using: .utf8)!
]
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
// Read a generic password
let readQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "[email protected]",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let readStatus = SecItemCopyMatching(readQuery as CFDictionary, &result)
let data = result as? Data
// Update a generic password
let updateQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "[email protected]"
]
let updateAttributes: [String: Any] = [
kSecValueData as String: "newSecret".data(using: .utf8)!
]
let updateStatus = SecItemUpdate(updateQuery as CFDictionary, updateAttributes as CFDictionary)
// Delete a generic password
let deleteQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "[email protected]"
]
let deleteStatus = SecItemDelete(deleteQuery as CFDictionary)
SecItem 函数
SecItemAdd
func SecItemAdd(_ attributes: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus
attributes 字典接受:项目类 + 项目属性 + 值属性 + 返回类型属性。
不接受搜索属性(kSecMatch*)。在添加查询中提供 kSecMatchLimit 会导致错误。
result:如果不需要取回添加的项目,请传入 nil。传入指针可接收按 kSecReturn* 键指定格式返回的项目。大多数情况下传入 nil 即可——请求返回结果会强制执行额外读取。
SecItemCopyMatching
func SecItemCopyMatching(_ query: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus
query 字典接受:项目类 + 项目属性 + 搜索属性 + 返回类型属性。
不接受将值属性(kSecValueData)用作搜索条件。
result:类型取决于设置了哪些 kSecReturn* 键:
- 仅
kSecReturnData→CFData - 仅
kSecReturnAttributes→CFDictionary - 仅
kSecReturnRef→SecKey/SecCertificate/SecIdentity - 仅
kSecReturnPersistentRef→CFData(持久引用) - 多个
kSecReturn*键 → 包含所请求类型的CFDictionary kSecMatchLimit = kSecMatchLimitAll→ 包含上述对象的CFArray
SecItemUpdate
func SecItemUpdate(_ query: CFDictionary, _ attributesToUpdate: CFDictionary) -> OSStatus
query 字典接受:项目类 + 项目属性 + 搜索属性。用于查找要更新的项目。
attributesToUpdate 字典接受:项目属性 + 值属性。这些属性会应用于匹配的项目。不接受项目类或搜索属性。
更新 kSecValueData 会替换存储的机密。更新属性(例如 kSecAttrLabel)会更改元数据,但不会影响机密。
SecItemDelete
func SecItemDelete(_ query: CFDictionary) -> OSStatus
query 字典接受:项目类 + 项目属性 + 搜索属性。
在 macOS 上,默认删除所有匹配项目(隐式使用 kSecMatchLimitAll)。在 iOS 上也会删除所有匹配项。删除不会要求确认,且会立即执行。
项目类
kSecClassGenericPassword
通用机密存储。最常用的类。
| 属性 | 键 | 类型 |
|---|---|---|
| 服务 | kSecAttrService | CFString |
| 账户 | kSecAttrAccount | CFString |
| 访问组 | kSecAttrAccessGroup | CFString |
| 可访问性 | kSecAttrAccessible | CFString(常量) |
| 可同步 | kSecAttrSynchronizable | CFBoolean |
| 标签 | kSecAttrLabel | CFString |
| 描述 | kSecAttrDescription | CFString |
| 注释 | kSecAttrComment | CFString |
| 通用 | kSecAttrGeneric | CFData |
| 创建者 | kSecAttrCreator | CFNumber(FourCharCode) |
| 类型 | kSecAttrType | CFNumber(FourCharCode) |
| 创建日期 | kSecAttrCreationDate | CFDate(只读) |
| 修改日期 | kSecAttrModificationDate | CFDate(只读) |
kSecClassInternetPassword
与 URL 关联的凭据。很少需要使用——大多数应用使用通用密码。
| 属性 | 键 | 类型 |
|---|---|---|
| 服务器 | kSecAttrServer | CFString |
| 协议 | kSecAttrProtocol | CFString(常量) |
| 端口 | kSecAttrPort | CFNumber |
| 路径 | kSecAttrPath | CFString |
| 账户 | kSecAttrAccount | CFString |
| 身份验证类型 | kSecAttrAuthenticationType | CFString(常量) |
| 安全域 | kSecAttrSecurityDomain | CFString |
| 可访问性 | kSecAttrAccessible | CFString(常量) |
| 访问组 | kSecAttrAccessGroup | CFString |
| 可同步 | kSecAttrSynchronizable | CFBoolean |
| 标签 | kSecAttrLabel | CFString |
| 注释 | kSecAttrComment | CFString |
| 创建者 | kSecAttrCreator | CFNumber(FourCharCode) |
| 类型 | kSecAttrType | CFNumber(FourCharCode) |
kSecClassCertificate
X.509 证书。通常由系统管理,而非应用代码管理。
| 属性 | 键 | 类型 |
|---|---|---|
| 主体 | kSecAttrSubject | CFData(只读) |
| 颁发者 | kSecAttrIssuer | CFData(只读) |
| 序列号 | kSecAttrSerialNumber | CFData(只读) |
| 主体密钥 ID | kSecAttrSubjectKeyID | CFData(只读) |
| 公钥哈希 | kSecAttrPublicKeyHash | CFData(只读) |
| 证书类型 | kSecAttrCertificateType | CFNumber |
| 证书编码 | kSecAttrCertificateEncoding | CFNumber |
| 标签 | kSecAttrLabel | CFString |
| 访问组 | kSecAttrAccessGroup | CFString |
| 可同步 | kSecAttrSynchronizable | CFBoolean |
kSecClassKey
加密密钥(RSA、EC、AES)。用于加密、签名和密钥协商。
| 属性 | 键 | 类型 |
|---|---|---|
| 密钥类别 | kSecAttrKeyClass | CFString(常量) |
| 应用标签 | kSecAttrApplicationLabel | CFData |
| 应用标签 | kSecAttrApplicationTag | CFData |
| 密钥类型 | kSecAttrKeyType | CFString(常量) |
| 密钥大小(位) | kSecAttrKeySizeInBits | CFNumber |
| 有效密钥大小 | kSecAttrEffectiveKeySize | CFNumber |
| 永久性 | kSecAttrIsPermanent | CFBoolean |
| 敏感性 | kSecAttrIsSensitive | CFBoolean |
| 可提取 | kSecAttrIsExtractable | CFBoolean |
| 标签 | kSecAttrLabel | CFString |
| 访问组 | kSecAttrAccessGroup | CFString |
| 可同步 | kSecAttrSynchronizable | CFBoolean |
| 令牌 ID | kSecAttrTokenID | CFString |
kSecClassIdentity
数字身份是与其私钥配对的证书。它不是独立的存储类别,而是由系统根据匹配的证书和密钥合成生成。你不能直接添加 kSecClassIdentity 项;应分别添加证书和密钥。当两者共享相同的 kSecAttrPublicKeyHash 时,查询会返回一个身份。
参见 Quinn “The Eskimo!” 的技术说明:“SecItem: Pitfalls and Best Practices”(forums/thread/724013)——数字身份是虚拟联接,而非存储项。
各类别的唯一性约束
每个钥匙串项都由其部分属性唯一标识。添加主键相同的第二个项会返回 errSecDuplicateItem(-25299)。使用 SecItemUpdate 修改现有项。
| 类别 | 主键属性 |
|---|---|
| 通用密码 | kSecAttrService + kSecAttrAccount + kSecAttrAccessGroup + kSecAttrSynchronizable |
| 互联网密码 | kSecAttrServer + kSecAttrPort + kSecAttrProtocol + kSecAttrAuthenticationType + kSecAttrPath + kSecAttrAccount + kSecAttrAccessGroup + kSecAttrSynchronizable |
| 证书 | kSecAttrCertificateType + kSecAttrIssuer + kSecAttrSerialNumber + kSecAttrAccessGroup + kSecAttrSynchronizable |
| 密钥 | kSecAttrApplicationLabel + kSecAttrApplicationTag + kSecAttrKeyType + kSecAttrKeySizeInBits + kSecAttrEffectiveKeySize + kSecAttrKeyClass + kSecAttrAccessGroup + kSecAttrSynchronizable |
| 身份 | N/A(证书 + 密钥的虚拟联接) |
结果:如果你在相同的 kSecAttrService 下为多个用户存储令牌,却没有为 kSecAttrAccount 设置唯一值,那么第二个用户调用 SecItemAdd 时会返回 errSecDuplicateItem。
属性常量参考
身份属性
| 常量 | 类型 | 用途类别 |
|---|---|---|
kSecAttrService | CFString | GenericPassword |
kSecAttrAccount | CFString | GenericPassword、InternetPassword |
kSecAttrServer | CFString | InternetPassword |
kSecAttrLabel | CFString | 所有类别 |
kSecAttrDescription | CFString | GenericPassword、InternetPassword |
kSecAttrComment | CFString | GenericPassword、InternetPassword |
kSecAttrGeneric | CFData | GenericPassword |
安全属性
| 常量 | 类型 | 用途 |
|---|---|---|
kSecAttrAccessible | CFString(常量) | 所有类别 |
kSecAttrAccessControl | SecAccessControl | 所有类别 |
kSecAttrAccessGroup | CFString | 所有类别 |
kSecAttrSynchronizable | CFBoolean | 所有类别 |
kSecAttrAccessible 和 kSecAttrAccessControl 互斥。两者同时设置会报错——创建 kSecAttrAccessControl 时已包含可访问性级别。
令牌属性
| 常量 | 类型 | 用途 |
|---|---|---|
kSecAttrTokenID | CFString | 将密钥绑定到硬件令牌 |
kSecAttrTokenIDSecureEnclave | CFString(值) | Secure Enclave——仅支持 EC 密钥(256 位) |
密钥元数据属性
| 常量 | 类型 | 值 |
|---|---|---|
kSecAttrKeyType | CFString | kSecAttrKeyTypeRSA、kSecAttrKeyTypeECSECPrimeRandom |
kSecAttrKeySizeInBits | CFNumber | 256(EC)、2048/4096(RSA) |
kSecAttrKeyClass | CFString | kSecAttrKeyClassPublic、kSecAttrKeyClassPrivate、kSecAttrKeyClassSymmetric |
kSecAttrApplicationTag | CFData | 用于查找密钥的应用自定义标签 |
kSecAttrApplicationLabel | CFData | 公钥的 SHA-1 哈希值(自动生成) |
搜索属性
用于 SecItemCopyMatching、SecItemUpdate(查询参数)和 SecItemDelete 查询。
| 常量 | 类型 | 用途 |
|---|---|---|
kSecMatchLimit | CFString 或 CFNumber | 最大结果数——kSecMatchLimitOne、kSecMatchLimitAll,或使用 CFNumber 指定整数上限(例如,最多返回 5 个结果) |
kSecMatchCaseInsensitive | CFBoolean | 匹配字符串属性时不区分大小写 |
kSecMatchLimit 默认值
默认值取决于上下文,是常见的错误来源:
| 函数 | 默认值 | 行为 |
|---|---|---|
SecItemCopyMatching | kSecMatchLimitOne | 返回第一个匹配项 |
SecItemDelete | 所有匹配项 | 删除每个匹配项 |
在 SecItemCopyMatching 中始终显式设置 kSecMatchLimit,以明确意图。对于 SecItemDelete,省略 kSecMatchLimit 会删除所有匹配项——这是预期行为,并非错误。
返回类型属性
控制 SecItemCopyMatching 和 SecItemAdd 的返回内容。在查询字典中设置。
| 常量 | 返回内容 | 结果类型 |
|---|---|---|
kSecReturnData | 机密(密码字节、密钥数据) | CFData |
kSecReturnAttributes | 项目元数据字典 | CFDictionary |
kSecReturnRef | 钥匙串对象引用 | SecKey、SecCertificate 或 SecIdentity |
kSecReturnPersistentRef | 持久引用(应用重新启动后仍有效) | CFData |
各类别的返回类型行为
| 类别 | kSecReturnData | kSecReturnRef |
|---|---|---|
| 通用密码 | 密码字节 | 不适用(无引用类型) |
| 互联网密码 | 密码字节 | 不适用(无引用类型) |
| 证书 | DER 编码的证书数据 | SecCertificate |
| 密钥 | 密钥数据(如果可提取) | SecKey |
| 身份 | 不适用 | SecIdentity |
多种返回类型
当多个 kSecReturn* 键设为 true 时,结果为 CFDictionary,包含以下键:
kSecValueData→ 数据kSecValueRef→ 引用kSecValuePersistentRef→ 持久引用- 若
kSecReturnAttributes为true,还会包含所有属性键
设置 kSecMatchLimitAll 时,结果为包含上述内容的 CFArray。
值类型属性
用于在添加、查询和更新字典中提供或提取值。
| 常量 | 类型 | 用途 |
|---|---|---|
kSecValueData | CFData | 机密(密码、密钥材料) |
kSecValueRef | SecKey / SecCertificate / SecIdentity | 钥匙串对象引用 |
kSecValuePersistentRef | CFData | 项目的持久引用 |
各操作的行为
| 属性 | SecItemAdd | SecItemCopyMatching | SecItemUpdate |
|---|---|---|---|
kSecValueData | 设置密钥内容 | 不能用作搜索条件 | 替换密钥内容 |
kSecValueRef | 添加所引用的对象 | 按引用查找 | 无效 |
kSecValuePersistentRef | 无效 | 按持久化引用查找 | 无效 |
辅助功能常量
控制何时可以读取钥匙串项目。通过 kSecAttrAccessible 设置。
| 常量 | 可用时机 | 是否在备份中保留 | 是否通过 iCloud 同步 |
|---|---|---|---|
kSecAttrAccessibleWhenUnlocked | 设备已解锁 | 是 | 是(默认) |
kSecAttrAccessibleAfterFirstUnlock | 首次解锁后至重启前 | 是 | 是 |
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly | 设备已解锁且已设置密码 | 否 | 否 |
kSecAttrAccessibleWhenUnlockedThisDeviceOnly | 设备已解锁 | 否 | 否 |
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly | 首次解锁后至重启前 | 否 | 否 |
默认值:新项目使用 kSecAttrAccessibleWhenUnlocked。
ThisDeviceOnly 变体:项目不会包含在加密备份中,也不会通过 iCloud 钥匙串同步。用于绑定到设备的密钥(生物识别验证的令牌、Secure Enclave 密钥)。
WhenPasscodeSetThisDeviceOnly:如果用户移除设备密码,项目会被删除。用于必须在移除密码后失效的密钥。
AfterFirstUnlock:用户在重启后首次解锁设备后,即可在后台使用。后台抓取、推送通知处理程序和后台 URLSession 完成回调都需要此选项。
已弃用(请勿使用):kSecAttrAccessibleAlways、kSecAttrAccessibleAlwaysThisDeviceOnly。
SecAccessControlCreateFlags
对钥匙串项目进行精细的访问控制。使用 SecAccessControlCreateWithFlags 创建,并通过 kSecAttrAccessControl 设置。
所有标志
| 标志 | 用途 |
|---|---|
.userPresence | 任意生物识别方式或设备密码 |
.biometryAny | 任意已录入的生物识别信息(新增录入后仍有效) |
.biometryCurrentSet | 仅限当前生物识别信息集(生物识别信息变更后失效) |
.devicePasscode | 需要设备密码 |
.privateKeyUsage | Secure Enclave 密钥签名操作所需 |
.applicationPassword | 应用提供的密码(附加于其他验证因素) |
.watch | 配对的 Apple Watch 可用于完成验证 |
.or | 通过逻辑 OR 组合标志(满足任意一项即可) |
.and | 通过逻辑 AND 组合标志(必须满足所有条件) |
创建访问控制
var error: Unmanaged<CFError>?
guard let accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.biometryCurrentSet, .or, .devicePasscode],
&error
) else {
let nsError = error!.takeRetainedValue() as Error
fatalError("Failed to create access control: \(nsError)")
}
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "auth-token",
kSecAttrAccessControl as String: accessControl,
kSecValueData as String: tokenData
]
let status = SecItemAdd(query as CFDictionary, nil)
标志组合
| 组合 | 含义 |
|---|---|
[.biometryAny] | 任意已录入的指纹或面容 |
[.biometryCurrentSet] | 当前指纹或面容集(重新录入后失效) |
[.biometryCurrentSet, .or, .devicePasscode] | 生物识别验证或设备密码回退 |
[.biometryCurrentSet, .and, .applicationPassword] | 生物识别验证和应用密码 |
[.privateKeyUsage] | Secure Enclave 密钥操作(签名、解密) |
[.biometryAny, .or, .watch] | 生物识别验证或配对的 Watch |
.biometryAny 与 .biometryCurrentSet:高安全性项目(银行令牌)使用 .biometryCurrentSet。如果用户录入新指纹,项目将无法访问——应用必须重新验证并重新存储。对于新增录入不应导致访问失效的便利用途项目,请使用 .biometryAny。
LocalAuthentication 集成
将 LAContext 与钥匙串配合使用
先使用 LAContext 预先评估生物识别信息,再将该上下文传递给钥匙串查询,以避免再次弹出生物识别验证提示。
import LocalAuthentication
let context = LAContext()
context.localizedReason = "Access your credentials"
var authError: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) else {
// Biometrics unavailable — handle error or fall back to passcode
return
}
context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Authenticate to access credentials"
) { success, error in
guard success else { return }
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "auth-token",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseAuthenticationContext as String: context
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
}
LAContext 钥匙串键
| 键 | 类型 | 用途 |
|---|---|---|
kSecUseAuthenticationContext | LAContext | 重用已通过身份验证的上下文(避免重复提示) |
kSecUseAuthenticationUI | CFString | 控制 UI 行为:kSecUseAuthenticationUIAllow(默认)、kSecUseAuthenticationUIFail、kSecUseAuthenticationUISkip |
kSecUseAuthenticationUIFail:返回 errSecInteractionNotAllowed,而不是显示生物识别提示。可用于检查项目是否存在,同时不触发 UI。
LAPolicy 类型
| 策略 | 要求 |
|---|---|
.deviceOwnerAuthenticationWithBiometrics | 仅 Face ID 或 Touch ID |
.deviceOwnerAuthentication | 生物识别或密码回退 |
BiometryType 检测
let context = LAContext()
var error: NSError?
context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
switch context.biometryType {
case .faceID: // Face ID available
case .touchID: // Touch ID available
case .opticID: // Optic ID available (visionOS)
case .none: // No biometric hardware
@unknown default: break
}
LAError 错误代码
| 错误 | 代码 | 原因 |
|---|---|---|
.authenticationFailed | -1 | 用户身份验证失败 |
.userCancel | -2 | 用户轻点了“取消” |
.userFallback | -3 | 用户轻点了“输入密码” |
.systemCancel | -4 | 系统已取消(应用进入后台) |
.passcodeNotSet | -5 | 未设置密码 |
.biometryNotAvailable | -6 | 硬件不可用或受到限制 |
.biometryNotEnrolled | -7 | 未录入生物识别信息 |
.biometryLockout | -8 | 失败次数过多 |
OSStatus 错误代码
常见钥匙串 OSStatus 值及其根本原因。
| 错误 | 代码 | 描述 | 常见原因 |
|---|---|---|---|
errSecSuccess | 0 | 操作成功 | — |
errSecDuplicateItem | -25299 | 项目已存在 | 添加了相同主键的项目——请改用 SecItemUpdate |
errSecItemNotFound | -25300 | 未找到匹配项目 | 查询属性错误,或项目从未存储 |
errSecInteractionNotAllowed | -25308 | UI 提示被阻止 | 项目需要身份验证,但设备已锁定,或设置了 kSecUseAuthenticationUIFail |
errSecAuthFailed | -25293 | 身份验证失败 | 密码错误、生物识别失败或 ACL 拒绝 |
errSecMissingEntitlement | -34018 | 缺少钥匙串权限 | 应用缺少 keychain-access-groups 权限——单元测试目标中很常见 |
errSecNoSuchAttr | -25303 | 未找到属性 | 查询的属性对该项目类无效 |
errSecParam | -50 | 参数无效 | 查询字典格式错误——请检查类型是否不匹配(例如,预期为 Data 却传入 String) |
errSecAllocate | -108 | 内存分配失败 | 系统资源耗尽 |
errSecDecode | -26275 | 无法解码数据 | 项目损坏或编码不匹配 |
errSecNotAvailable | -25291 | 钥匙串不可用 | 没有钥匙串数据库(少见——模拟器已重置或安装损坏) |
在 Swift 中解读 OSStatus
let status = SecItemAdd(query as CFDictionary, nil)
if status != errSecSuccess {
let message = SecCopyErrorMessageString(status, nil) as? String ?? "Unknown error"
print("Keychain error \(status): \(message)")
}
测试目标中的 -34018
单元测试运行器(XCTest)通常缺少 keychain-access-groups 权限。解决方法:
- 为测试目标添加宿主应用(Xcode → 测试目标 → 通用 → 宿主应用)
- 单元测试使用内存模拟对象,仅在集成测试中使用真实钥匙串
钥匙串共享
访问组
默认情况下,项目按应用隔离。若要在应用或扩展之间共享:
- 在 Xcode 中启用“钥匙串共享”功能
- 添加共享访问组标识符
- 添加项目时设置
kSecAttrAccessGroup
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.shared",
kSecAttrAccount as String: "shared-token",
kSecAttrAccessGroup as String: "TEAMID.com.example.shared",
kSecValueData as String: tokenData
]
访问组格式为 $(TeamIdentifierPrefix)$(GroupIdentifier)。未显式指定访问组的项目,默认使用其权限中的第一个应用访问组。
iCloud 钥匙串同步
将 kSecAttrSynchronizable 设为 true,即可通过 iCloud 钥匙串同步:
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "sync-token",
kSecAttrSynchronizable as String: true,
kSecValueData as String: tokenData
]
可同步项目不能使用 ThisDeviceOnly 可访问性级别或 SecAccessControl。必须使用 kSecAttrAccessibleWhenUnlocked 或 kSecAttrAccessibleAfterFirstUnlock。
查询时,kSecAttrSynchronizable 默认值为 kSecAttrSynchronizableAny(返回本地项目和已同步项目)。显式设为 true 或 false 可进行筛选。
Resources
WWDC: 2013-709, 2014-711, 2020-10147
Docs: /security/keychain_services, /localauthentication, /security/secaccesscontrolcreateflags, /security/secitemadd(::)
Skills: axiom-code-signing-ref, axiom-app-attest