21

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.

Ketan P
  • 4,259
  • 3
  • 30
  • 36
matthewpalmer
  • 2,619
  • 3
  • 25
  • 27
  • 1
    I'm still waiting to get this work and none the wiser. In fact, with the latest version of Xcode, I'm getting an error message with the use of 'NSDictionary'. It really shouldn't be this frustrating! Good luck ... I'll be watching closely. – Darren Jun 20 '14 at 10:11
  • 1
    @Darren I think I've gotten it working. Check out the edited version above. I wasn't converting the NSString input into the proper NSData, and I hadn't added the actual data value to the keychainQuery. After changing this, I then had to change the `load` method to decode the NSData response. Let me know if that helps you :) – matthewpalmer Jun 20 '14 at 22:36
  • Which version of Xcode are you using? I've tried your code in Beta-2, and I keep getting the following error message `Could not find an overload for 'init' that accepts the supplied arguments` from the definition of `keychainQuery`. This has only started happening to me since I changed to beta-2. Thanks for any reply. – Darren Jun 21 '14 at 03:59
  • I think I might be on the first beta (Version 6.0 (6A215l)). Have you tried setting the key/value pairs using any of the other available methods? Might just be `NSMutableDictionary(objects:keys:)` specifically that's buggy. – matthewpalmer Jun 21 '14 at 04:19
  • 1
    matt - Are you planning on making a Cocoapod out of this new Library? I'd love to try it out, I really don't want to import it with a submodule :) – James Armstead Dec 30 '14 at 06:01
  • @JamesArmstead Yeah, I think I will once Cocoapods hits 1.0 and adds Swift dependency support. Feel free to open an issue on the Github repo if you want it sooner though :) – matthewpalmer Dec 30 '14 at 20:24
  • Re: "// I'm not too sure what's happening here..." I just brought this code up with a Swift guru at WWDC. What's happening is that you're taking the Unmanaged pointer and extracting a COpaquePointer? which is essentially an optional void*. This lets you actually compare the pointer value, to make sure it's not NULL. If Sec failed to return anything, dataTypeRef wouldn't actually be nil, but the wrapped pointer would be null. – Oded Jun 11 '15 at 20:17
  • @matthewpalmer Can you confirm that using your library to store usernames and passwords will pass the Apple Store encryption standards? – rolling_codes May 12 '16 at 16:15
  • @matthewpalmer do you think you could help me with this question?: http://stackoverflow.com/questions/38211806/swift-locksmith-not-getting-stored-keychain-value – JamesG Jul 05 '16 at 20:50

5 Answers5

7

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

Tiago Mendes
  • 4,572
  • 1
  • 29
  • 35
  • Thanks very much. The class compiles without ant errors. But I'm not sure how to use it. Can you include an example please? And maybe a little explanation on what the methods in the class (`saveToken`, `load` etc) are and what they're supposed to do? – Isuru Aug 10 '14 at 20:42
  • Thanks for your help! I've updated [my original blog post](http://matthewpalmer.net/blog/2014/06/21/example-ios-keychain-swift-save-query/) with these changes and a couple of other minor things. @Isuru my post should have a simple example usage. Let me know if it works for you :) – matthewpalmer Aug 10 '14 at 22:35
  • @matt Hi, I was following that example. In the `viewDidLoad` method, I save a value like this - `KeychainService.saveToken("Some value")` and in another button press, I retrieve it like this - `label.text = KeychainService.loadToken()`. But I get the following error - **Nothing was retrieved from the keychain. Status code -25300** – Isuru Aug 13 '14 at 06:19
  • @Isuru in `private class save(...)`, right at the end of the function, add `println("Saved to the keychain. Status code \(status)")`. What's the output now? The status code -25300 indicates that the item couldn't be found in the keychain ([reference—scroll to end](https://developer.apple.com/library/ios/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/doc/uid/TP30000898-CH5g-CJBEABHG)) – matthewpalmer Aug 13 '14 at 06:24
  • @matt I think I must have missed something. I copied the updated code from your blogpost and tried the same code and it worked. I have one last question. How do you save multiple values? Because there is no such thing like key value pairs in this, right? How do I save different values and retrieve them separately? – Isuru Aug 13 '14 at 06:48
  • 1
    @Isuru I think you'd need to use a different value for the key `kSecAttrAccountValue` wherever needed. This is just a string, so you'd modify `load` and `save` to accept the arguments, use the arguments, etc. – matthewpalmer Aug 13 '14 at 09:32
  • Use `.takeUnretainedValue()` because these are system constants and you should not be adjusting their retain counts. – Alexsander Akers Oct 21 '14 at 13:19
  • takeUnretainedValue() is not supported in xcode 6.1.1 – Shai Dec 08 '14 at 12:26
5

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

let keychain = KeychainSwift()
keychain.set("hello world", forKey: "my key")
keychain.get("my key")
keychain.delete("my key")
Evgenii
  • 36,389
  • 27
  • 134
  • 170
3

For Swift users

Single line code to add/retrieve/update fields in Keychain:
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")
K.K
  • 2,647
  • 1
  • 26
  • 32
3

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/

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Vitalii
  • 4,267
  • 1
  • 40
  • 45
1

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).

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

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
JAL
  • 41,701
  • 23
  • 172
  • 300
matthewpalmer
  • 2,619
  • 3
  • 25
  • 27
  • hi , I am getting the following error when using your code. http://stackoverflow.com/questions/24453808/how-to-create-a-nsmutabledictionary-in-swift?noredirect=1#comment37843816_24453808 – Farhad-Taran Jun 27 '14 at 16:11
  • I get an error saying **Extra argument 'objects' in call** in 2 places. This line `var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, dataFromString], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecValueData])` in the `save` function and this line, `var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, kCFBooleanTrue, kSecMatchLimitOne], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData, kSecMatchLimit])` in the load function. – Isuru Jul 27 '14 at 19:34
  • @Isuru I've been having a lot of trouble with this code depending on which version of the beta I'm on, so I'm not sure how to fix it sorry. In the meantime, I'm using Sam Soffes' SSKeychain to access the keychain. – matthewpalmer Jul 27 '14 at 23:26