使用 Swift 向 iOS 钥匙串添加项目和查询
Posted
技术标签:
【中文标题】使用 Swift 向 iOS 钥匙串添加项目和查询【英文标题】:Adding Items to and Querying the iOS Keychain with Swift 【发布时间】:2014-08-11 00:51:01 【问题描述】:我无法将所有可用于从ios Keychain
添加数据和查询数据的Objective C
代码示例转换为Swift。我正在尝试对字符串(访问令牌)进行基本存储并将其读回。我已经查看了有关 Stack Overflow 的其他一些问题,但我无法让它发挥作用。我试图从各种来源拼凑出一个解决方案。
编辑 1: 我尝试了更基本的设置,因为我认为我的 self.defaultKeychainQuery 可能把事情搞砸了。我已将以下代码更新为最新版本。
编辑 2: 搞定了。我没有正确地将数据值添加到保存查询中。我需要将字符串转换为 NSData。我已将以下代码更新为最新的工作版本。
编辑 3: 正如 Xerxes 在下面指出的那样,由于字典的某些问题,此代码不适用于高于 Beta 1 的 Xcode 版本。如果您知道解决此问题的方法,请告诉我。
更新:我把它变成了keychain library written in Swift called Locksmith。
保存
class func save(service: NSString, data: NSString)
var dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
// Instantiate a new default keychain query
var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, dataFromString], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecValueData])
// Delete any existing items
SecItemDelete(keychainQuery as CFDictionaryRef)
// Add the new keychain item
var status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)
// Check that it worked ok
println("Saving status code is: \(status)")
加载
class func load(service: NSString) -> AnyObject?
// Instantiate a new default keychain query
// Tell the query to return a result
// Limit our results to one item
var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, kCFBooleanTrue, kSecMatchLimitOne], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData, kSecMatchLimit])
// I'm not too sure what's happening here...
var dataTypeRef :Unmanaged<AnyObject>?
// Search for the keychain items
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
println("Loading status code is: \(status)")
// I'm not too sure what's happening here...
let opaque = dataTypeRef?.toOpaque()
if let op = opaque?
let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
println("Retrieved the following data from the keychain: \(retrievedData)")
var str = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
println("The decoded string is \(str)")
else
println("Nothing was retrieved from the keychain.")
return nil
用法(视图控制器)
KeychainService.saveToken("sometoken")
KeychainService.loadToken()
使用这些便捷方法
class func saveToken(token: NSString)
self.save("service", data: token)
class func loadToken()
var token = self.load("service")
if let t = token
println("The token is: \(t)")
这导致控制台中的输出:
Saving status code is: 0
Loading status code is: 0
Retrieved the following data from the keychain: <736f6d65 746f6b65 6e>
The decoded string is sometoken
非常感谢您的帮助。我不太确定一旦获得 dataTypeRef 后如何处理它,或者它是否有上面代码给出的任何数据。
【问题讨论】:
我仍在等待完成这项工作,但没有比这更明智的了。事实上,在最新版本的 Xcode 中,使用“NSDictionary”时我会收到一条错误消息。真的不应该这么沮丧!祝你好运......我会密切关注。 @Darren 我想我已经成功了。查看上面的编辑版本。我没有将 NSString 输入转换为正确的 NSData,也没有将实际数据值添加到 keychainQuery。更改此设置后,我不得不更改load
方法来解码 NSData 响应。让我知道这是否对您有帮助:)
您使用的是哪个版本的 Xcode?我已经在 Beta-2 中尝试过您的代码,并且从keychainQuery
的定义中不断收到以下错误消息Could not find an overload for 'init' that accepts the supplied arguments
。自从我改用 beta-2 后,这才开始发生在我身上。感谢您的回复。
我想我可能是第一个测试版(版本 6.0 (6A215l))。您是否尝试过使用任何其他可用方法设置键/值对?可能只是NSMutableDictionary(objects:keys:)
,特别是那是错误的。
matt - 你打算用这个新图书馆制作一个 Cocoapod 吗?我很想尝试一下,我真的不想用子模块导入它:)
【参考方案1】:
为了让它工作,你需要检索钥匙串常量的保留值,然后首先像这样存储:
let kSecClassValue = kSecClass.takeRetainedValue() as NSString
let kSecAttrAccountValue = kSecAttrAccount.takeRetainedValue() as NSString
let kSecValueDataValue = kSecValueData.takeRetainedValue() as NSString
let kSecClassGenericPasswordValue = kSecClassGenericPassword.takeRetainedValue() as NSString
let kSecAttrServiceValue = kSecAttrService.takeRetainedValue() as NSString
let kSecMatchLimitValue = kSecMatchLimit.takeRetainedValue() as NSString
let kSecReturnDataValue = kSecReturnData.takeRetainedValue() as NSString
let kSecMatchLimitOneValue = kSecMatchLimitOne.takeRetainedValue() as NSString
然后您可以像这样引用 NSMutableDictionary 中的值:
var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
我写了一篇关于它的博客文章: http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/
希望这会有所帮助!
谢尔比
【讨论】:
非常感谢。该类编译没有 ant 错误。但我不确定如何使用它。你能举个例子吗?或许可以稍微解释一下类中的方法(saveToken
、load
等)是什么以及它们应该做什么?
感谢您的帮助!我已经更新了my original blog post 的这些更改和其他一些小事情。 @Isuru 我的帖子应该有一个简单的示例用法。让我知道它是否适合你:)
@matt 嗨,我按照那个例子。在viewDidLoad
方法中,我保存一个像这样的值-KeychainService.saveToken("Some value")
,然后在另一个按钮按下时,我像这样检索它-label.text = KeychainService.loadToken()
。但我收到以下错误 - 没有从钥匙串中检索到任何内容。状态码-25300
@Isuru 在private class save(...)
,就在函数的最后,添加println("Saved to the keychain. Status code \(status)")
。现在有什么输出?状态码 -25300 表示在钥匙串中找不到该项目 (reference—scroll to end)
@Isuru 我认为您需要在需要时为键 kSecAttrAccountValue
使用不同的值。这只是一个字符串,因此您需要修改 load
和 save
以接受参数、使用参数等。【参考方案2】:
我为这个简单的任务编写了一个演示应用程序和辅助函数:为 Keychain 中的给定键写入/读取文本字符串。
https://github.com/marketplacer/keychain-swift
let keychain = KeychainSwift()
keychain.set("hello world", forKey: "my key")
keychain.get("my key")
keychain.delete("my key")
【讨论】:
【参考方案3】:我对如何添加、获取、删除密码的解释(对于那些懒惰使用此线程中提供的库的人):
// Saving password associated with the login and service
let userAccount = "user's login"
let service = "service name"
let passwordData: NSData = self.textfield_password.text!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let keychainQuery: [NSString: NSObject] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: userAccount,
kSecAttrService: service,
kSecValueData: passwordData]
SecItemDelete(keychainQuery as CFDictionaryRef) //Deletes the item just in case it already exists
let keychain_save_status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)
print("Keychain saving code is: \(keychain_save_status)")
...
// Getting the password associated with the login and service
let userAccount = "user's login"
let service = "service name"
let keychainQuery: [NSString: NSObject] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: userAccount,
kSecReturnData: kCFBooleanTrue,
kSecMatchLimit: kSecMatchLimitOne]
var rawResult: AnyObject?
let keychain_get_status: OSStatus = SecItemCopyMatching(keychainQuery, &rawResult)
print("Keychain getting code is: \(keychain_get_status)")
if (keychain_get_status == errSecSuccess)
let retrievedData = rawResult as? NSData
let pass = NSString(data: retrievedData!, encoding: NSUTF8StringEncoding)
print("Username: \(userAccount), password: \(pass!)")
// Do your work with the retrieved password here
else
print("No login data found in Keychain.")
...
//Deleting user's credentials from Keychain. Password is optional for the query when you delete, in most cases you won't know it after all.
let userAccount = "user's login"
let service = "service name"
let keychainQuery: [NSString: NSObject] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: userAccount,
kSecAttrService: service]
let keychain_delete_status: OSStatus = SecItemDelete(keychainQuery as CFDictionaryRef)
print("Keychain deleting code is: \(keychain_delete_status)")
结果代码和其他有用信息可以在官方文档中找到:https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/
【讨论】:
【参考方案4】:对于 Swift 用户
在钥匙串中添加/检索/更新字段的单行代码:https://github.com/jrendel/SwiftKeychainWrapper
用法
将字符串值添加到钥匙串:
let saveSuccessful: Bool = KeychainWrapper.setString("Some String", forKey: "myKey")
从钥匙串中检索字符串值:
let retrievedString: String? = KeychainWrapper.stringForKey("myKey")
从钥匙串中移除一个字符串值:
let removeSuccessful: Bool = KeychainWrapper.removeObjectForKey("myKey")
【讨论】:
【参考方案5】:我想我已经找到了解决方案。我在上面编辑了我的帖子以包含有效的代码(至少对我而言)。我也在这里写过博客:using the iOS Keychain with Swift (example code)。
8 月 11 日更新:我已根据 rshelby 的 cmets 更新了博客文章中的代码。看看吧。
更新:我把它变成了keychain library written in Swift called Locksmith。
保存
class func save(service: NSString, data: NSString)
var dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
// Instantiate a new default keychain query
var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, dataFromString], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecValueData])
// Delete any existing items
SecItemDelete(keychainQuery as CFDictionaryRef)
// Add the new keychain item
var status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)
// Check that it worked ok
println("Saving status code is: \(status)")
加载
class func load(service: NSString) -> AnyObject?
// Instantiate a new default keychain query
// Tell the query to return a result
// Limit our results to one item
var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, kCFBooleanTrue, kSecMatchLimitOne], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData, kSecMatchLimit])
// I'm not too sure what's happening here...
var dataTypeRef :Unmanaged<AnyObject>?
// Search for the keychain items
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
println("Loading status code is: \(status)")
// I'm not too sure what's happening here...
let opaque = dataTypeRef?.toOpaque()
if let op = opaque?
let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
println("Retrieved the following data from the keychain: \(retrievedData)")
var str = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
println("The decoded string is \(str)")
else
println("Nothing was retrieved from the keychain.")
return nil
用法(视图控制器)
KeychainService.saveToken("sometoken")
KeychainService.loadToken()
使用这些便捷方法
class func saveToken(token: NSString)
self.save("service", data: token)
class func loadToken()
var token = self.load("service")
if let t = token
println("The token is: \(t)")
这导致控制台中的输出:
Saving status code is: 0
Loading status code is: 0
Retrieved the following data from the keychain: <736f6d65 746f6b65 6e>
The decoded string is sometoken
【讨论】:
嗨,使用您的代码时出现以下错误。 ***.com/questions/24453808/… 我在 2 个地方收到错误提示 Extra argument 'objects' in call。save
函数中的这一行 var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, dataFromString], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecValueData])
和加载函数中的这一行 var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, kCFBooleanTrue, kSecMatchLimitOne], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData, kSecMatchLimit])
。
@Isuru 我在这段代码上遇到了很多麻烦,具体取决于我使用的 beta 版本,所以我不知道如何解决它抱歉。同时,我正在使用 Sam Soffes 的 SSKeychain 来访问钥匙串。以上是关于使用 Swift 向 iOS 钥匙串添加项目和查询的主要内容,如果未能解决你的问题,请参考以下文章
Xamarin.iOS VSTS 无法将临时钥匙串添加到钥匙串搜索路径