KeychainAccess
KeychainAccess 是一个简单的 Swift 包装器,用于 Keychain,可在 iOS 和 OS X 上运行。使使用 Keychain API 变得极其简单,并使其在 Swift 中更容易使用。
💡 功能
- 简单界面
- 支持访问组
- 支持辅助功能
- 支持 iCloud 共享
- 支持 TouchID 和 Keychain 集成(iOS 8+)
- 支持共享网络凭据(iOS 8+)
- 适用于 iOS 和 macOS
- 支持 watchOS 和 tvOS
- 支持 Mac Catalyst
- 兼容 Swift 3、4 和 5
📖 用法
👀 另请参阅
🔑 基础
保存应用密码
let keychain = Keychain(service: "com.example.github-token")
keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
保存网络密码
let keychain = Keychain(server: "https://github.com", protocolType: .https)
keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
🔑 实例化
为应用程序密码创建密钥链
let keychain = Keychain(service: "com.example.github-token")
let keychain = Keychain(service: "com.example.github-token", accessGroup: "12ABCD3E4F.shared")
为互联网密码创建密钥链
let keychain = Keychain(server: "https://github.com", protocolType: .https)
let keychain = Keychain(server: "https://github.com", protocolType: .https, authenticationType: .htmlForm)
🔑 添加项目
subscripting
for String
keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
keychain[string: "kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
for NSData
keychain[data: "secret"] = NSData(contentsOfFile: "secret.bin")
set method
keychain.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
error handling
do {
try keychain.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
}
catch let error {
print(error)
}
🔑 获取项目
下标索引
对 String(如果值是 NSData,尝试转换为 String)
let token = keychain["kishikawakatsumi"]
let token = keychain[string: "kishikawakatsumi"]
对 NSData
let secretData = keychain[data: "secret"]
获取方法
作为 String
let token = try? keychain.get("kishikawakatsumi")
let token = try? keychain.getString("kishikawakatsumi")
作为 NSData
let data = try? keychain.getData("kishikawakatsumi")
🔑 删除项目
下标索引
keychain["kishikawakatsumi"] = nil
移除方法
do {
try keychain.remove("kishikawakatsumi")
} catch let error {
print("error: \(error)")
}
🔑 设置标签和注释
let keychain = Keychain(server: "https://github.com", protocolType: .https)
do {
try keychain
.label("github.com (kishikawakatsumi)")
.comment("github access token")
.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
} catch let error {
print("error: \(error)")
}
🔑 获取其他属性
PersistentRef
let keychain = Keychain()
let persistentRef = keychain[attributes: "kishikawakatsumi"]?.persistentRef
...
创建日期
let keychain = Keychain()
let creationDate = keychain[attributes: "kishikawakatsumi"]?.creationDate
...
所有属性
let keychain = Keychain()
do {
let attributes = try keychain.get("kishikawakatsumi") { $0 }
print(attributes?.comment)
print(attributes?.label)
print(attributes?.creator)
...
} catch let error {
print("error: \(error)")
}
下标
let keychain = Keychain()
if let attributes = keychain[attributes: "kishikawakatsumi"] {
print(attributes.comment)
print(attributes.label)
print(attributes.creator)
}
🔑 配置(无障碍功能、共享、iCloud 同步)
提供流畅的接口
let keychain = Keychain(service: "com.example.github-token")
.label("github.com (kishikawakatsumi)")
.synchronizable(true)
.accessibility(.afterFirstUnlock)
无障碍功能
默认无障碍功能匹配背景应用程序(=kSecAttrAccessibleAfterFirstUnlock)
let keychain = Keychain(service: "com.example.github-token")
为后台应用程序
创建实例
let keychain = Keychain(service: "com.example.github-token")
.accessibility(.afterFirstUnlock)
keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
一次性
let keychain = Keychain(service: "com.example.github-token")
do {
try keychain
.accessibility(.afterFirstUnlock)
.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
} catch let error {
print("error: \(error)")
}
为前台应用程序
创建实例
let keychain = Keychain(service: "com.example.github-token")
.accessibility(.whenUnlocked)
keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
一次性
let keychain = Keychain(service: "com.example.github-token")
do {
try keychain
.accessibility(.whenUnlocked)
.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
} catch let error {
print("error: \(error)")
}
👫 共享密钥链项目
let keychain = Keychain(service: "com.example.github-token", accessGroup: "12ABCD3E4F.shared")
🔄 使用iCloud同步密钥链项目
创建实例
let keychain = Keychain(service: "com.example.github-token")
.synchronizable(true)
keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
一次性操作
let keychain = Keychain(service: "com.example.github-token")
do {
try keychain
.synchronizable(true)
.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
} catch let error {
print("error: \(error)")
}
🌀 Touch ID (Face ID)集成
需要认证的操作必须在后台线程中运行。
如果在主线程中运行,UI线程将会锁定,以便系统尝试显示认证对话框。
要使用Face ID,请将NSFaceIDUsageDescription
键添加到你的Info.plist
文件中。
🔐 添加受Touch ID (Face ID)保护的项
如果要存储受Touch ID protected Keychain项,请指定accessibility
和authenticationPolicy
属性。
let keychain = Keychain(service: "com.example.github-token")
DispatchQueue.global().async {
do {
// Should be the secret invalidated when passcode is removed? If not then use `.WhenUnlocked`
try keychain
.accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .userPresence)
.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
} catch let error {
// Error handling if needed...
}
}
🔐 更新受Touch ID (Face ID)保护的项
与添加时相同的方式。
如果存在您要尝试添加的项已经存在且受保护的可能性,请在主线程外运行。 因为更新受保护项目需要认证。
另外,当更新时还想显示自定义认证提示消息,指定一个authenticationPrompt
属性。如果该项不受保护,忽略authenticationPrompt
参数。
let keychain = Keychain(service: "com.example.github-token")
DispatchQueue.global().async {
do {
// Should be the secret invalidated when passcode is removed? If not then use `.WhenUnlocked`
try keychain
.accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .userPresence)
.authenticationPrompt("Authenticate to update your access token")
.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
} catch let error {
// Error handling if needed...
}
}
🔐 获取受Touch ID (Face ID)保护的项
与获取普通项时相同的方式。如果尝试获取的项是受保护的,将自动显示Touch ID或密码认证。
如果要显示自定义认证提示消息,指定一个authenticationPrompt
属性。如果该项不受保护,则忽略authenticationPrompt
参数。
let keychain = Keychain(service: "com.example.github-token")
DispatchQueue.global().async {
do {
let password = try keychain
.authenticationPrompt("Authenticate to login to server")
.get("kishikawakatsumi")
print("password: \(password)")
} catch let error {
// Error handling if needed...
}
}
🔐 删除受Touch ID (Face ID)保护的项
与删除普通项时相同的方式。删除Keychain项时没有显示Touch ID或密码认证的方法。
let keychain = Keychain(service: "com.example.github-token")
do {
try keychain.remove("kishikawakatsumi")
} catch let error {
// Error handling if needed...
}
🔑 共享Web凭证
共享Web凭证是一个程序接口,允许原生iOS应用与其相应的网站共享凭证。例如,用户可以在Safari中登录网站,输入用户名和密码,并使用iCloud密钥链保存这些凭证。稍后,用户可以运行同一开发者的原生应用,而无需用户重新输入用户名和密码,共享Web凭证使其能够访问之前在Safari中输入的凭证。用户还可以在应用内创建新账户、更新密码或删除其账户。这些更改随后将由Safari保存并使用。
https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/
let keychain = Keychain(server: "https://www.kishikawakatsumi.com", protocolType: .HTTPS)
let username = "[email protected]"
// First, check the credential in the app's Keychain
if let password = try? keychain.get(username) {
// If found password in the Keychain,
// then log into the server
} else {
// If not found password in the Keychain,
// try to read from Shared Web Credentials
keychain.getSharedPassword(username) { (password, error) -> () in
if password != nil {
// If found password in the Shared Web Credentials,
// then log into the server
// and save the password to the Keychain
keychain[username] = password
} else {
// If not found password either in the Keychain also Shared Web Credentials,
// prompt for username and password
// Log into server
// If the login is successful,
// save the credentials to both the Keychain and the Shared Web Credentials.
keychain[username] = inputPassword
keychain.setSharedPassword(inputPassword, account: username)
}
}
}
请求所有关联域的凭证
Keychain.requestSharedWebCredential { (credentials, error) -> () in
}
生成强随机密码
生成与Safari自动填充相同格式的强随机密码(xxx-xxx-xxx-xxx)。
let password = Keychain.generatePassword() // => Nhu-GKm-s3n-pMx
如何设置共享Web凭证
将com.apple.developer.associated-domains许可添加到您的应用中。此许可必须包括您想要共享凭证的所有域。
将apple-app-site-association文件添加到您的网站中。此文件必须包含网站想要共享凭证的所有应用的标识符,并且必须正确签名。
当应用安装时,系统将下载并验证其关联的每个域的网站关联文件。如果验证成功,应用将与域名相关联。
更多详细信息
https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/
🔍 调试
打印密钥链对象时显示所有存储项
let keychain = Keychain(server: "https://github.com", protocolType: .https)
print("\(keychain)")
=>
[
[authenticationType: default, key: kishikawakatsumi, server: github.com, class: internetPassword, protocol: https]
[authenticationType: default, key: hirohamada, server: github.com, class: internetPassword, protocol: https]
[authenticationType: default, key: honeylemon, server: github.com, class: internetPassword, protocol: https]
]
获取所有存储密钥
let keychain = Keychain(server: "https://github.com", protocolType: .https)
let keys = keychain.allKeys()
for key in keys {
print("key: \(key)")
}
=>
key: kishikawakatsumi
key: hirohamada
key: honeylemon
获取所有存储项
let keychain = Keychain(server: "https://github.com", protocolType: .https)
let items = keychain.allItems()
for item in items {
print("item: \(item)")
}
=>
item: [authenticationType: Default, key: kishikawakatsumi, server: github.com, class: InternetPassword, protocol: https]
item: [authenticationType: Default, key: hirohamada, server: github.com, class: InternetPassword, protocol: https]
item: [authenticationType: Default, key: honeylemon, server: github.com, class: InternetPassword, protocol: https]
密钥链共享功能
如果您遇到以下错误,则需要添加Keychain.entitlements
。
OSStatus error:[-34018] Internal error when a required entitlement isn't present, client has neither application-identifier nor keychain-access-groups entitlements.
需求
操作系统 | Swift | |
---|---|---|
v1.1.x | iOS 7+, macOS 10.9+ | 1.1 |
v1.2.x | iOS 7+, macOS 10.9+ | 1.2 |
v2.0.x | iOS 7+, macOS 10.9+, watchOS 2+ | 2.0 |
v2.1.x | iOS 7+, macOS 10.9+, watchOS 2+ | 2.0 |
v2.2.x | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1 |
v2.3.x | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1, 2.2 |
v2.4.x | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 2.2, 2.3 |
v3.0.x | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 3.x |
v3.1.x | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 4.0, 4.1, 4.2 |
v3.2.x | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 4.0, 4.1, 4.2, 5.0 |
v4.0.x | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 4.0, 4.1, 4.2, 5.1 |
v4.1.x | iOS 8+, macOS 10.9+, watchOS 3+, tvOS 9+, Mac Catalyst 13+ | 4.0, 4.1, 4.2, 5.1 |
安装
CocoaPods
KeychainAccess 通过 CocoaPods 提供。要安装它,只需在您的 Podfile 中添加以下行
use_frameworks!
pod 'KeychainAccess'
Carthage
KeychainAccess 也通过 Carthage 提供。要安装它,只需在您的 Cartfile 中添加以下行
github "kishikawakakumi/KeychainAccess"
Swift Package Manager
KeychainAccess 同样也通过 Swift Package Manager 提供。
Xcode
选择 文件 > Swift 包 > 添加包依赖...
,
CLI
首先,创建包含其包声明的 Package.swift
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "MyLibrary",
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"]),
],
dependencies: [
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", from: "3.0.0"),
],
targets: [
.target(name: "MyLibrary", dependencies: ["KeychainAccess"]),
]
)
然后,输入
$ swift build
将手动添加到您的项目中
- 将
Lib/KeychainAccess.xcodeproj
添加到项目中 - 将
KeychainAccess.framework
链接到您的目标 - 将
Copy Files Build Phase
添加到您的应用程序包中,包括框架
请参考iOS示例项目。
作者
kishikawa katsumi, [email protected]
许可证
KeychainAccess根据MIT许可证提供。有关更多信息,请参阅LICENSE文件。