Search code examples
iosswiftlocal-storagekeychain

Adding Items to and Querying the iOS Keychain with Swift


I'm having trouble converting all of the Objective C code samples that are available for adding data and querying data from the iOS Keychain into 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.

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.

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.

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.

Update: I turned this into a keychain library written in Swift called 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.


Solution

  • 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:

    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/

    Hope this helps!

    rshelby