使用 Swift 在 iOS 钥匙串中添加和查询项目

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24324285/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:00:05  来源:igfitidea点击:

Adding Items to and Querying the iOS Keychain with Swift

iosswiftlocal-storagekeychain

提问by matthewpalmer

I'm having trouble converting all of the Objective Ccode samples that are available for adding data and querying data from the iOS Keychaininto Swift. I'm trying to do a basic storage of a string (an access token) and reading it back. I've had a look at some of the other questions on Stack Overflow, but I can't quite get it to work. I've tried to piece together a solution from the various sources.

我无法将所有Objective C可用于添加数据和查询数据的代码示例转换iOS KeychainSwift。我正在尝试对字符串(访问令牌)进行基本存储并将其读回。我已经查看了有关 Stack Overflow 的其他一些问题,但我无法让它发挥作用。我试图从各种来源拼凑出一个解决方案。

Edit 1:I tried with a more basic setup, because I thought my self.defaultKeychainQuery might have been messing things up. I've updated the code below to the latest version.

编辑 1:我尝试了更基本的设置,因为我认为我的 self.defaultKeychainQuery 可能把事情搞砸了。我已将下面的代码更新到最新版本。

Edit 2:Got it working. I wasn't adding the data value to the save query properly. I needed to convert the string to NSData. I've updated the code below to the most recent working version.

编辑2:让它工作。我没有正确地将数据值添加到保存查询中。我需要将字符串转换为 NSData。我已将下面的代码更新为最新的工作版本。

Edit 3:As Xerxes points out below, this code doesn't work with Xcode versions higher than Beta 1 because of some issue with Dictionaries. If you know of a fix for this, please let me know.

编辑 3:正如 Xerxes 在下面指出的,由于词典存在一些问题,此代码不适用于高于 Beta 1 的 Xcode 版本。如果您知道解决此问题的方法,请告诉我。

Update: I turned this into a keychain library written in Swift called Locksmith.

更新:我把它变成了一个用 Swift 编写的名为 Locksmith钥匙串库



Save

节省

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)")
}

Load

加载

  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
  }

Usage (view controller)

用法(视图控制器)

KeychainService.saveToken("sometoken")
KeychainService.loadToken()

which uses these convenience methods

使用这些方便的方法

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)")
    }
  }

This leads to the output in the console:

这导致控制台中的输出:

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

Thanks a lot for your help. I'm not too sure what to do with dataTypeRef once I've got it, or if it has any data given the code above.

非常感谢你的帮助。我不太确定一旦得到 dataTypeRef 该怎么办,或者如果上面的代码有任何数据。

采纳答案by user3927134

In order to get this to work, you will need to retrieve the retained values of the keychain constants and store then first like so:

为了使其工作,您需要检索钥匙串常量的保留值并首先像这样存储:

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

You can then reference the values in the NSMutableDictionary like so:

然后,您可以像这样引用 NSMutableDictionary 中的值:

var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])

I wrote a blog post about it at: http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/

我写了一篇关于它的博客文章:http: //rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/

Hope this helps!

希望这可以帮助!

rshelby

谢尔比

回答by Evgenii

I wrote a demo app and helper functions for this simple task: writing/reading a text string for a given key in Keychain.

我为这个简单的任务编写了一个演示应用程序和辅助函数:为钥匙串中的给定键编写/读取文本字符串。

https://github.com/marketplacer/keychain-swift

https://github.com/marketplacer/keychain-swift

let keychain = KeychainSwift()
keychain.set("hello world", forKey: "my key")
keychain.get("my key")
keychain.delete("my key")

回答by K.K

For Swift users

对于 Swift 用户

Single line code to add/retrieve/update fields in Keychain:
https://github.com/jrendel/SwiftKeychainWrapper

在钥匙串中添加/检索/更新字段的单行代码:https:
//github.com/jrendel/SwiftKeychainWrapper

Usage

用法

Add a string value to keychain:

向钥匙串添加字符串值:

let saveSuccessful: Bool = KeychainWrapper.setString("Some String", forKey: "myKey")  

Retrieve a string value from keychain:

从钥匙串中检索字符串值:

let retrievedString: String? = KeychainWrapper.stringForKey("myKey")

Remove a string value from keychain:

从钥匙串中删除字符串值:

let removeSuccessful: Bool = KeychainWrapper.removeObjectForKey("myKey")

回答by Vitalii

My interpretation on how to add, get, delete passwords (for those who are lazy to use libraries presented in this thread):

我对如何添加、获取、删除密码的解释(对于那些懒得使用本线程中提供的库的人):

// 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)")

The result codes and other useful info can be found in the official documentation: https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/

结果代码和其他有用信息可以在官方文档中找到:https: //developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/

回答by matthewpalmer

I think I've worked out the solution. I've edited my post above to include the code that works (at least for me). I've also blogged about it here: using the iOS Keychain with Swift (example code).

我想我已经找到了解决方案。我在上面编辑了我的帖子以包含有效的代码(至少对我而言)。我还在这里写了一篇关于它的博客:使用 iOS 钥匙串和 Swift (示例代码)

Update 11 Aug: I've updated the code in the blog post based on rshelby's comments. Take a look.

8 月 11 日更新:我已根据 rshelby 的评论更新了博客文章中的代码。看一看。

Update: I turned this into a keychain library written in Swift called Locksmith.

更新:我把它变成了一个用 Swift 编写的名为 Locksmith钥匙串库



Save

节省

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)")
}

Load

加载

  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
  }

Usage (view controller)

用法(视图控制器)

KeychainService.saveToken("sometoken")
KeychainService.loadToken()

which uses these convenience methods

使用这些方便的方法

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)")
    }
  }

This leads to the output in the console:

这导致控制台中的输出:

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