Keychain — 是 iOS、watchOS、macOS 和 tvOS 中的安全凭据存储,在硬件级别管理密码、密钥和证书。与 UserDefaults 不同,Keychain 中的数据经过加密并由应用程序沙箱隔离。根据 Apple Developer Documentation,Keychain 使用 Secure Enclave 机制和 AES-256 硬件加密来保护机密信息。
要点
Keychain — 是 Apple 操作系统的安全数据库,旨在安全存储密码、加密密钥、证书和其他机密数据。Keychain 首次出现在 1999 年的 Mac OS 8.6 中,而在 iOS 中则从第一个 SDK 版本开始出现。
Keychain 解决了移动开发的基本问题:在不受保护的文件系统中存储秘密(令牌、密码、加密密钥)。与 UserDefaults 或 SQLite 不同,Keychain 中的数据以加密形式存储,并在写入磁盘时自动加密。
每个 iOS 应用程序在隔离的沙箱中运行,并且只能访问其自己的 keychain。不过,开发人员可以通过 keychain access groups 配置同一开发人员的应用程序之间对 Keychain 的共享访问。iCloud Keychain 用户还可以通过端到端加密在设备之间同步数据。
根据 Apple Security White Paper (2025),Keychain 使用与设备唯一标识符 (UID) 绑定的密钥进行 AES-256 硬件加密,这使得数据在从设备提取时无法访问。
Keychain 作为分层服务运作:应用程序调用 Security framework,该框架与管理加密 SQLite Keychain 数据库的 securityd 守护进程交互。每个条目使用单独的密钥加密,而密钥本身在配备 A7 芯片及更新设备的设备上受 Secure Enclave 硬件模块保护。
Security framework 提供用于操作 Keychain Services 的 C 接口。主要函数:SecItemAdd(添加)、SecItemCopyMatching(搜索)、SecItemUpdate(更新)和 SecItemDelete(删除)。每个操作接收一个描述请求类型的 CFDictionaryRef 属性字典。
数据根据类进行分类:kSecClassGenericPassword(任意密码)、kSecClassInternetPassword(互联网服务密码)、kSecClassCertificate(证书)、kSecClassKey(加密密钥)和 kSecClassIdentity(密钥与证书的组合)。
在保存密码时,应用程序创建一个包含属性(服务、账户、访问级别、标签)的字典。Security framework 将数据传递给 securityd 守护进程,该进程使用绑定到设备的密钥加密条目并保存到 Keychain SQLite 中。提取过程类似:根据属性查询、搜索、解密并返回数据。
一个重要特性:当应用程序被删除时,iOS 会自动清除 Keychain中由此应用程序创建的条目(从 iOS 10.3+ 开始)。重新安装应用程序时,除非使用了 iCloud Keychain,否则先前安装的 Keychain 数据将无法访问。
import Security
func savePassword(service: String, account: String, password: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecValueData as String: password,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
SecItemAdd(query as CFDictionary, nil)
}
Keychain 支持五种主要数据类,每种类都有自己的一组属性和应用领域。类的选择决定了哪些搜索键可用以及数据如何加密。
kSecClassGenericPassword 和 kSecClassInternetPassword 类用于存储文本密码。互联网版本还存储服务器数据:域名、协议、端口和身份验证路径。GenericPassword 用于存储任何密码,无需绑定到服务器。
在保存互联网密码时,可以指定 kSecAttrServer(域名)、kSecAttrProtocol(HTTPS、FTP 等)、kSecAttrPort 和 kSecAttrPath。这使得在通过 AutoFill 自动填充时能够精确识别条目。
kSecClassKey 类存储对称 (AES) 和非对称 (RSA、EC) 密钥。kSecClassCertificate 类存储 X.509 证书。kSecClassIdentity 类将私钥和相应证书合并为一个条目。
对于非对称密钥,Keychain 支持以下属性:kSecAttrKeyType (RSA、EC)、kSecAttrKeySizeInBits (2048、256)、kSecAttrIsPermanent(是否保存在 Keychain 中)。对于证书:kSecAttrLabel(人类可读名称)、kSecAttrSubject(所有者 DN)、kSecAttrIssuer(颁发者 DN)。
| Keychain 类 | 用途 | 数据类型 |
|---|---|---|
| kSecClassGenericPassword | 应用程序密码 | Data (NSString) |
| kSecClassInternetPassword | 服务器密码 | Data + 服务器属性 |
| kSecClassCertificate | X.509 证书 | SecCertificate |
| kSecClassKey | 加密密钥 | SecKey |
| kSecClassIdentity | 密钥 + 证书 | SecIdentity |
在原生 iOS 代码中与 Keychain 交互的主要接口——Security framework 的函数。对于 Swift 和 Objective-C,有一个 C 接口可用,包装在便捷函数中。
SecItemAdd 函数向 Keychain 添加新元素。输入接收一个包含类、数据和属性的字典。返回 errSecSuccess (0) 状态或错误代码。SecItemCopyMatching 函数根据属性执行搜索并返回找到的数据。
搜索使用 kSecReturnData 标志——如果为 true,函数返回条目的内容。kSecMatchLimit 标志确定结果数量:kSecMatchLimitOne(一个)或 kSecMatchLimitAll(全部)。
func readPassword(service: String, account: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else { return nil }
return item as? Data
}
SecItemUpdate 允许更改现有条目的属性或数据。SecItemDelete 删除与传递的字典匹配的所有条目。删除时,务必指定足够的属性,以免意外删除共享 keychain 中他人的条目。
func deletePassword(service: String, account: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account
]
SecItemDelete(query as CFDictionary)
}
每个 Keychain 条目都有属性 kSecAttrAccessible,它确定数据何时可读。这对安全性至关重要:错误的访问级别可能使数据变得脆弱,或者相反,在需要时无法访问。
kSecAttrAccessibleWhenUnlocked——数据仅在设备解锁时可访问。kSecAttrAccessibleAfterFirstUnlock——重启后首次解锁后可访问。kSecAttrAccessibleAlways——始终可访问(不推荐)。后缀 ThisDeviceOnly 禁止通过备份传输数据。
在配备 Secure Enclave 的设备上,Keychain 加密密钥可以额外受生物识别保护:kSecAccessControlBiometryCurrentSet 或 kSecAccessControlUserPresence。这为每次读取操作添加了 Face ID 或 Touch ID 要求。
iCloud Keychain 通过端到端加密在 Apple 设备之间同步条目。Apple 无法访问解密后的数据。要启用同步,请添加属性 kSecAttrSynchronizable,值为 kCFBooleanTrue。
重要事项:同步的条目不能包含 ThisDeviceOnly 属性,因为它们必须在其他设备上可访问。当所有设备丢失时,iCloud Keychain 可以通过恢复代码或 iCloud 备份密钥恢复。
对于身份验证令牌,使用 kSecAttrAccessibleWhenUnlockedThisDeviceOnly——数据仅在解锁设备上可用,不会复制到备份中。对于必须可供后台任务访问的证书,使用 kSecAttrAccessibleAfterFirstUnlock。切勿对机密数据使用 kSecAttrAccessibleAlways。
建议始终为不需要同步的数据添加 ThisDeviceOnly 后缀。这可以防止秘密意外复制到 iCloud 或 iTunes 备份中,在那里它们可能被分析工具提取出来。对于关键数据,将可用性与 kSecAccessControlUserPresence 结合使用——这会为解密条目添加生物识别或密码要求。
使用 iCloud Keychain 时,请注意同步的条目在用户的所有设备上都可用:如果攻击者获得对一个设备的访问权限,数据可能会在所有设备上泄露。对于隔离的秘密,请使用 ThisDeviceOnly。
常见问题
UserDefaults 以开放形式将数据存储在 .plist 文件中,在 IPA 分析时可访问。Keychain 单独加密每个条目,使用硬件加密,并支持访问级别、生物识别和通过 iCloud 同步。
在 iOS 模拟器上每次启动时,Keychain 都会被清除。在真实设备上,删除应用程序——iOS 将删除此开发者的所有 Keychain 条目。对于选择性清除,使用相应的属性调用 SecItemDelete。
是的,通过 keychain access groups。应用程序在签名中必须具有相同的团队 ID,并且 entitlements 中具有相同的 keychain-access-groups。开发者在 Xcode Capabilities 中配置共享访问。
Apple 没有记录严格的限制,但在实践中建议每个条目不超过 1–4 KB。对于大量数据,使用文件加密并将密钥存储在 Keychain 中,数据本身存储在 Documents 中。
在越狱后,Keychain 的保护会降低,因为攻击者获得 root 访问权限并可以直接读取 Keychain SQLite。对于关键数据,使用从服务器获取的密钥进行额外加密。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。