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