mcpField
A
axiom-keychain-ref

axiom-keychain-ref

github.com/charleswiltgen安全1.2k36 次安装今天更新
代码仓库

关于 axiom-keychain-ref

面向 iOS 和 macOS Keychain Services 开发者的 API 参考,详述 SecItem 操作、项目属性、唯一性规则、可访问性和返回值,帮助编写准确的查询与存储代码。

主要功能

  1. 01SecItem 增删改查函数行为
  2. 02Keychain 项目类别与属性
  3. 03各项目类别的唯一性规则
  4. 04搜索与返回属性参考
  5. 05可访问性和访问控制详情

适用场景

  1. 01编写 Keychain 增删改查查询
  2. 02依据唯一性规则诊断重复项目错误
  3. 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

通用机密存储。最常用的类。

属性键类型
服务kSecAttrServiceCFString
账户kSecAttrAccountCFString
访问组kSecAttrAccessGroupCFString
可访问性kSecAttrAccessibleCFString(常量)
可同步kSecAttrSynchronizableCFBoolean
标签kSecAttrLabelCFString
描述kSecAttrDescriptionCFString
注释kSecAttrCommentCFString
通用kSecAttrGenericCFData
创建者kSecAttrCreatorCFNumber(FourCharCode)
类型kSecAttrTypeCFNumber(FourCharCode)
创建日期kSecAttrCreationDateCFDate(只读)
修改日期kSecAttrModificationDateCFDate(只读)
kSecClassInternetPassword

与 URL 关联的凭据。很少需要使用——大多数应用使用通用密码。

属性键类型
服务器kSecAttrServerCFString
协议kSecAttrProtocolCFString(常量)
端口kSecAttrPortCFNumber
路径kSecAttrPathCFString
账户kSecAttrAccountCFString
身份验证类型kSecAttrAuthenticationTypeCFString(常量)
安全域kSecAttrSecurityDomainCFString
可访问性kSecAttrAccessibleCFString(常量)
访问组kSecAttrAccessGroupCFString
可同步kSecAttrSynchronizableCFBoolean
标签kSecAttrLabelCFString
注释kSecAttrCommentCFString
创建者kSecAttrCreatorCFNumber(FourCharCode)
类型kSecAttrTypeCFNumber(FourCharCode)
kSecClassCertificate

X.509 证书。通常由系统管理,而非应用代码管理。

属性键类型
主体kSecAttrSubjectCFData(只读)
颁发者kSecAttrIssuerCFData(只读)
序列号kSecAttrSerialNumberCFData(只读)
主体密钥 IDkSecAttrSubjectKeyIDCFData(只读)
公钥哈希kSecAttrPublicKeyHashCFData(只读)
证书类型kSecAttrCertificateTypeCFNumber
证书编码kSecAttrCertificateEncodingCFNumber
标签kSecAttrLabelCFString
访问组kSecAttrAccessGroupCFString
可同步kSecAttrSynchronizableCFBoolean
kSecClassKey

加密密钥(RSA、EC、AES)。用于加密、签名和密钥协商。

属性键类型
密钥类别kSecAttrKeyClassCFString(常量)
应用标签kSecAttrApplicationLabelCFData
应用标签kSecAttrApplicationTagCFData
密钥类型kSecAttrKeyTypeCFString(常量)
密钥大小(位)kSecAttrKeySizeInBitsCFNumber
有效密钥大小kSecAttrEffectiveKeySizeCFNumber
永久性kSecAttrIsPermanentCFBoolean
敏感性kSecAttrIsSensitiveCFBoolean
可提取kSecAttrIsExtractableCFBoolean
标签kSecAttrLabelCFString
访问组kSecAttrAccessGroupCFString
可同步kSecAttrSynchronizableCFBoolean
令牌 IDkSecAttrTokenIDCFString
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。


属性常量参考

身份属性
常量类型用途类别
kSecAttrServiceCFStringGenericPassword
kSecAttrAccountCFStringGenericPassword、InternetPassword
kSecAttrServerCFStringInternetPassword
kSecAttrLabelCFString所有类别
kSecAttrDescriptionCFStringGenericPassword、InternetPassword
kSecAttrCommentCFStringGenericPassword、InternetPassword
kSecAttrGenericCFDataGenericPassword
安全属性
常量类型用途
kSecAttrAccessibleCFString(常量)所有类别
kSecAttrAccessControlSecAccessControl所有类别
kSecAttrAccessGroupCFString所有类别
kSecAttrSynchronizableCFBoolean所有类别

kSecAttrAccessible 和 kSecAttrAccessControl 互斥。两者同时设置会报错——创建 kSecAttrAccessControl 时已包含可访问性级别。

令牌属性
常量类型用途
kSecAttrTokenIDCFString将密钥绑定到硬件令牌
kSecAttrTokenIDSecureEnclaveCFString(值)Secure Enclave——仅支持 EC 密钥(256 位)
密钥元数据属性
常量类型值
kSecAttrKeyTypeCFStringkSecAttrKeyTypeRSA、kSecAttrKeyTypeECSECPrimeRandom
kSecAttrKeySizeInBitsCFNumber256(EC)、2048/4096(RSA)
kSecAttrKeyClassCFStringkSecAttrKeyClassPublic、kSecAttrKeyClassPrivate、kSecAttrKeyClassSymmetric
kSecAttrApplicationTagCFData用于查找密钥的应用自定义标签
kSecAttrApplicationLabelCFData公钥的 SHA-1 哈希值(自动生成)

搜索属性

用于 SecItemCopyMatching、SecItemUpdate(查询参数)和 SecItemDelete 查询。

常量类型用途
kSecMatchLimitCFString 或 CFNumber最大结果数——kSecMatchLimitOne、kSecMatchLimitAll,或使用 CFNumber 指定整数上限(例如,最多返回 5 个结果)
kSecMatchCaseInsensitiveCFBoolean匹配字符串属性时不区分大小写
kSecMatchLimit 默认值

默认值取决于上下文,是常见的错误来源:

函数默认值行为
SecItemCopyMatchingkSecMatchLimitOne返回第一个匹配项
SecItemDelete所有匹配项删除每个匹配项

在 SecItemCopyMatching 中始终显式设置 kSecMatchLimit,以明确意图。对于 SecItemDelete,省略 kSecMatchLimit 会删除所有匹配项——这是预期行为,并非错误。


返回类型属性

控制 SecItemCopyMatching 和 SecItemAdd 的返回内容。在查询字典中设置。

常量返回内容结果类型
kSecReturnData机密(密码字节、密钥数据)CFData
kSecReturnAttributes项目元数据字典CFDictionary
kSecReturnRef钥匙串对象引用SecKey、SecCertificate 或 SecIdentity
kSecReturnPersistentRef持久引用(应用重新启动后仍有效)CFData
各类别的返回类型行为
类别kSecReturnDatakSecReturnRef
通用密码密码字节不适用(无引用类型)
互联网密码密码字节不适用(无引用类型)
证书DER 编码的证书数据SecCertificate
密钥密钥数据(如果可提取)SecKey
身份不适用SecIdentity
多种返回类型

当多个 kSecReturn* 键设为 true 时,结果为 CFDictionary,包含以下键:

  • kSecValueData → 数据
  • kSecValueRef → 引用
  • kSecValuePersistentRef → 持久引用
  • 若 kSecReturnAttributes 为 true,还会包含所有属性键

设置 kSecMatchLimitAll 时,结果为包含上述内容的 CFArray。


值类型属性

用于在添加、查询和更新字典中提供或提取值。

常量类型用途
kSecValueDataCFData机密(密码、密钥材料)
kSecValueRefSecKey / SecCertificate / SecIdentity钥匙串对象引用
kSecValuePersistentRefCFData项目的持久引用
各操作的行为
属性SecItemAddSecItemCopyMatchingSecItemUpdate
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需要设备密码
.privateKeyUsageSecure 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 钥匙串键
键类型用途
kSecUseAuthenticationContextLAContext重用已通过身份验证的上下文(避免重复提示)
kSecUseAuthenticationUICFString控制 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 值及其根本原因。

错误代码描述常见原因
errSecSuccess0操作成功—
errSecDuplicateItem-25299项目已存在添加了相同主键的项目——请改用 SecItemUpdate
errSecItemNotFound-25300未找到匹配项目查询属性错误,或项目从未存储
errSecInteractionNotAllowed-25308UI 提示被阻止项目需要身份验证,但设备已锁定,或设置了 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 权限。解决方法:

  1. 为测试目标添加宿主应用(Xcode → 测试目标 → 通用 → 宿主应用)
  2. 单元测试使用内存模拟对象,仅在集成测试中使用真实钥匙串

钥匙串共享

访问组

默认情况下,项目按应用隔离。若要在应用或扩展之间共享:

  1. 在 Xcode 中启用“钥匙串共享”功能
  2. 添加共享访问组标识符
  3. 添加项目时设置 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

评论

支持 Markdown