使用Swift添加项目和查询iOS Keychain

时间:2014-06-20 09:39:59

标签: ios swift local-storage keychain

我无法转换所有可用于添加数据并将数据从Objective C查询到 Swift iOS Keychain代码示例。我试图对字符串(访问令牌)进行基本存储并将其读回。我已经看过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之后该如何处理,或者如果上面的代码有任何数据。

5 个答案:

答案 0 :(得分:7)

为了使其工作,你需要检索钥匙串常量的保留值,然后先存储,如下所示:

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/

希望这有帮助!

rshelby

答案 1 :(得分:5)

我为这个简单的任务编写了一个演示应用程序和辅助函数:在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")

答案 2 :(得分:3)

对于Swift用户

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

<强>用法

将字符串值添加到钥匙串:

  final AutoCompleteTextView textView = (AutoCompleteTextView) v.findViewById(R.id.listview4);
  ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_spinner_dropdown_item, somearray);
            textView.setTextColor(Color.BLACK);
            textView.setThreshold(1);
            textView.setAdapter(adapter);

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

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

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

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

答案 3 :(得分: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 :(得分:1)

我想我已经找到了解决方案。我已经编辑了上面的帖子,包括有效的代码(至少对我而言)。我也在这里写了博客:using the iOS Keychain with Swift (example code)

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

更新:我将其转换为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