在钥匙串中存储机密
安全地保存和获取凭据。
在钥匙串中存储机密 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。
为什么需要钥匙串
密码、令牌和密钥绝不能放在 UserDefaults 或普通文件中,因为这些内容很容易被读取。钥匙串是由 OS 管理的加密数据库,用于存储少量机密数据,并受到硬件和用户密码的保护。它是存放凭据的唯一正确位置。
import Security
// Keychain stores secrets encrypted at rest,
// survives app updates, and gates access by policy.项目是字典
钥匙串服务 API 基于 C 语言:您需要使用包含 kSec... 常量键的 [String: Any] 查询字典来描述项目。同一种字典结构可重复用于添加、搜索、更新和删除。
import Security
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "user@example.com",
kSecAttrService as String: "com.example.app"
]
_ = query项目类别
kSecClass 键用于选择项目类型。kSecClassGenericPassword 用于存储应用令牌和机密数据;kSecClassInternetPassword 用于存储带有主机和协议属性的服务器凭据。大多数应用机密数据都使用通用密码类型。
import Security
// kSecClassGenericPassword -> tokens, API keys
// kSecClassInternetPassword -> server logins
// kSecClassKey / Certificate -> crypto material
let cls = kSecClassGenericPassword
_ = cls添加项目
SecItemAdd 会插入新项目。您需要将数据作为 Data 放在 kSecValueData 下,并提供用于标识项目的属性。它会返回一个 OSStatus;errSecSuccess 表示操作成功。
import Security
func save(_ token: String, account: String) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecValueData as String: Data(token.utf8)
]
return SecItemAdd(query as CFDictionary, nil)
== errSecSuccess
}处理重复项目
如果添加项目时其标识属性已经存在,就会返回 errSecDuplicateItem。稳健的保存逻辑会先尝试 SecItemAdd,遇到重复项目后再回退到 SecItemUpdate,这是一种 upsert 模式。
import Security
func upsert(_ data: Data, account: String) -> Bool {
let base: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account]
var add = base
add[kSecValueData as String] = data
let status = SecItemAdd(add as CFDictionary, nil)
if status == errSecDuplicateItem {
return SecItemUpdate(base as CFDictionary,
[kSecValueData as String: data] as CFDictionary)
== errSecSuccess
}
return status == errSecSuccess
}读取项目
SecItemCopyMatching 会执行搜索。若要获取机密数据的字节,必须将 kSecReturnData 设置为 true,并将 kSecMatchLimit 设置为 kSecMatchLimitOne。结果会通过输出参数以 CFTypeRef 的形式返回。
import Security
func load(account: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne]
var result: CFTypeRef?
let status = SecItemCopyMatching(
query as CFDictionary, &result)
guard status == errSecSuccess else { return nil }
return result as? Data
}更新项目
SecItemUpdate 接受两个字典:一个用于查找项目的查询字典,以及一个用于指定要更新属性的字典。只有您列出的属性会发生变化,其他所有内容都会保留。
import Security
func update(_ newData: Data, account: String) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account]
let attrs: [String: Any] = [
kSecValueData as String: newData]
return SecItemUpdate(query as CFDictionary,
attrs as CFDictionary) == errSecSuccess
}删除项目
SecItemDelete 会移除匹配的项目。删除不存在的项目会返回 errSecItemNotFound;在注销时清除凭据,可以将这种结果视为成功。
import Security
func delete(account: String) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account]
let status = SecItemDelete(query as CFDictionary)
return status == errSecSuccess
|| status == errSecItemNotFound
}解析 OSStatus
错误以整数形式的 OSStatus 代码表示。SecCopyErrorMessageString 会将其中一个代码转换为人类可读的描述,这在调试保存或读取失败的原因时非常有价值。
import Security
func describe(_ status: OSStatus) -> String {
return SecCopyErrorMessageString(status, nil)
as String? ?? "OSStatus \(status)"
}唯一标识项目
项目通过属性组合进行匹配——对于通用密码,通常是 kSecAttrService 加上 kSecAttrAccount。请选择稳定且专用于本应用的服务字符串,以避免不同机密数据之间发生冲突。
import Security
// Uniqueness for generic passwords usually comes from:
// service (your bundle id) + account (the username)
let service = "com.example.app.auth"
let account = "current-user"
_ = (service, account)小型封装
由于原始 API 十分冗长,团队通常会将其封装在一个公开 save、read 和 delete 的小型类型中。这样可以将 kSec 的样板代码集中在一个地方,同时让调用位置保持简洁。
import Security
struct TokenStore {
let service = "com.example.app.auth"
func read(_ account: String) -> Data? {
let q: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne]
var out: CFTypeRef?
return SecItemCopyMatching(q as CFDictionary, &out)
== errSecSuccess ? out as? Data : nil
}
}快速检查
请回忆一下,获取已存储机密数据字节的正确方法。
回顾
您已经学习了钥匙串的增删改查:
- 将凭据存储在加密的钥匙串中,绝不要存储在 UserDefaults 或文件中。
- 使用
kSec查询字典描述项目,并选择类似kSecClassGenericPassword的类别。 SecItemAdd/SecItemCopyMatching/SecItemUpdate/SecItemDelete覆盖整个生命周期;使用插入或更新操作处理errSecDuplicateItem。- 通过 service + account 标识项目,并解析
OSStatus以进行调试。
常见问题解答
「在钥匙串中存储机密」课时是免费的吗?
是的 — 「在钥匙串中存储机密」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。
「在钥匙串中存储机密」这节课中我会学到什么?
安全地保存和获取凭据。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Swift Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「在钥匙串中存储机密」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Swift Academy 课中编写并运行代码吗?
能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。