iOS Keychain 凭据安全存储

举报
shenlan9755 发表于 2026/10/02 10:08:45 2026/10/02
【摘要】 iOS Keychain 凭据安全存储摘要:登录令牌、密钥和小型敏感凭据不适合保存在普通偏好设置中。Keychain 提供系统管理的加密凭据存储能力。本文介绍 Keychain 的适用范围、增删查改、访问属性与错误处理。 一、区分偏好设置与敏感凭据UserDefaults 适合保存主题、开关和上次选中的页面等偏好信息,不应存放访问令牌、刷新令牌或私钥。Keychain 面向少量敏感数据,支...

iOS Keychain 凭据安全存储

摘要:登录令牌、密钥和小型敏感凭据不适合保存在普通偏好设置中。Keychain 提供系统管理的加密凭据存储能力。本文介绍 Keychain 的适用范围、增删查改、访问属性与错误处理。

一、区分偏好设置与敏感凭据

UserDefaults 适合保存主题、开关和上次选中的页面等偏好信息,不应存放访问令牌、刷新令牌或私钥。Keychain 面向少量敏感数据,支持访问组、设备状态约束和系统级保护。

Keychain 不是通用数据库,不适合存储大量结构化记录、缓存图片或随手保存任意业务数据。应用仍要定义令牌过期、撤销、退出登录清理和设备迁移规则。

二、定义稳定的凭据标识

Keychain 项通常由服务名、账户名和访问组等属性定位。将这些标识集中定义,避免读取和删除时拼写不一致:

enum CredentialStore {
    static let service = "app.session"
    static let account = "refresh-token"
}

不要把令牌内容拼进服务名或账户名。标识用于查找项目,凭据本身作为受保护的数据保存。

三、保存数据

使用 Security 框架的查询字典添加数据:

func save(_ token: Data) throws {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: CredentialStore.service,
        kSecAttrAccount as String: CredentialStore.account,
        kSecValueData as String: token,
        kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
    ]

    let status = SecItemAdd(query as CFDictionary, nil)
    guard status == errSecSuccess else {
        throw KeychainError(status: status)
    }
}

可访问性属性决定何时允许读取以及凭据是否可随备份迁移。后台需要访问的令牌和仅在设备解锁后使用的秘密,应采用不同策略。按数据敏感度和后台需求选择,而不是统一套用一种属性。

四、查询数据

查找时补充返回数据与返回数量要求:

func load() throws -> Data? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: CredentialStore.service,
        kSecAttrAccount as String: CredentialStore.account,
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]

    var result: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &result)
    if status == errSecItemNotFound { return nil }
    guard status == errSecSuccess else { throw KeychainError(status: status) }
    return result as? Data
}

“不存在”通常是正常业务状态,应与 Keychain 调用失败分开。调用成功后还要验证返回类型,避免不安全的强制转换。

五、更新与删除

更新使用匹配条件与属性字典调用 SecItemUpdate。若项目不存在,可按需求插入新项;这类“更新或插入”逻辑应显式处理并发调用。删除使用同一组稳定匹配属性调用 SecItemDelete,可将“已经不存在”视为幂等成功。

不要把更新失败一律当成插入。权限错误、设备锁定或参数错误可能需要暴露给上层,而不是被掩盖。

六、错误与并发封装

Security API 返回 OSStatus。封装层应保留状态码用于诊断,并转换为有限的业务错误类型。日志可以记录操作类型和状态分类,但不要打印密钥或令牌字节。

Keychain API 是同步调用。凭据数据通常很小,但封装边界应避免在高频 UI 渲染路径上反复读取;可在认证流程启动时读取一次,并将内存中的令牌生命周期控制在需要的范围。

七、访问控制与设备迁移

若凭据必须受 Face ID、Touch ID 或设备密码保护,可通过访问控制属性为读取增加用户验证条件。这样会影响后台刷新和自动登录流程,需针对实际体验设计。ThisDeviceOnly 类型的数据不会随设备备份迁移,适合绑定当前设备的秘密。

若启用 Keychain Access Group 在应用扩展间共享凭据,应缩小共享范围并检查签名配置。共享会扩大可访问主体,不能只因“以后可能用到”就启用。

八、测试与排查

使用唯一测试服务名隔离测试项。测试结束后删除测试凭据,避免不同用例相互污染。覆盖新增、读取、不存在、更新、删除和错误状态。设备重启、首次解锁前后及应用卸载重装行为,也应依据所选访问属性验证。

九、总结

Keychain 适合保存少量敏感凭据,正确使用还需要稳定的查询标识、合适的可访问性、完整的状态码处理和明确的退出清理。把系统存储细节封装在小型服务中,能让认证逻辑更清楚,也让安全策略更容易审查。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。