# Wrapping up the iOS Keychain
What to expect
Using the keychain on iOS, when talking to fellow developers, always seems to be a tedious thing. If you’re not familiar with those APIs it might seem complex and opaque. But it really isn’t, let me show you in a very short example how you can leverage the iOS keychain. Tailored to your needs with just a few lines of code and without any third party dependencies.
let keychain = KeychainWrapper(service: "myService")
// Setting the Password for the Username
keychain["username"] = "12345"
// Retrieving the password for the Username
let password = keychain["username"]
In case you can’t wait, I’ve created a sample project here which contains the KeychainWrapper
as well as a simple iOS App to try it out.
The KeychainWrapper
I’m going to present to you will be as easy to use as the UserDefaults
.
It will be a pretty minimal implementation of the existing iOS Keychain features and you might want to add more functionality to it depending on your needs, but I’m pretty sure in most of the simple use-cases this implementation will be absolutely sufficient.
Query all the things
First of all, let’s look at the keychainQuery
which we’ll be using for either creating new, updating existing or deleting keychain items within our App. It’ll be vital for working with any keychain operation.
[
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
kSecAttrSynchronizable as String: kCFBooleanTrue!
]
We’re only going to deal with passwords to be stored as credentials here, so we’re going to set the class item to kSecClassGenericPassword
which basically tells the keychain that this is item is a generic password, this has implications on the attributes we’re going to use.
The first attribute we’re going to define is called kSecAttrService
which describe the service (or scope) we’re going to use this item in, you could e.g. set this to the consumer of the credentials, for example you’re App’s “Loggedin-User-Only”-Area etc.
The attribute kSecAttrAccount
we’ll be using for the user’s username in this case.
Another important attribute is kSecAttrAccessible
which restricts certain usage of the keychaim item, e.g. if the device is locked and has never been unlocked, for example after a reboot. There is a variety of possible values for this attribute, we’re going to use kSecAttrAccessibleAfterFirstUnlock
which means that the devices needs to be unlocked at least once for the item to be used.
And at last we’re going to set kSecAttrSynchronizable
to true
to allow the item be synchronized to other devices using iCloud.
Creating items
To insert items into the keychain, we’re first going to check if an item with the same username is already existing, if it isn’t we’re going to insert one using the query properties we’ve defined previously.
guard SecItemCopyMatching(query as CFDictionary, nil) == noErr else {
query[kSecValueData as String] = data
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecSuccess
}
Now let’s see what’s going here, SecItemCopyMatching
is querying for an existing item in the keychain and, if successful, returning it. In our case if this fails, which means the item is not yet existing, we’re going to add a new item using SecItemAdd
which takes our predefined query plus the added kSecValueData
which is the actual password we’re going to store for this username.
As we’re dealing with CoreFoundation
here we have to cast our Swift dictionaries to CFDictionary
.
We’re also not providing the second, optional, parameter to SecItemAdd
which would return a pointer to the newly created object.
Updating items
if SecItemCopyMatching(query, nil) == noErr {
return SecItemUpdate(
query as CFDictionary,
NSDictionary(dictionary: [kSecValueData: data])
) == errSecSuccess
}
To update existing items (after checking for existence) just pass in the query and an updated dictionary containing the new password (kSecValueData
).
Deleting items
if SecItemCopyMatching(query, nil) == noErr {
return SecItemDelete(query as CFDictionary) == noErr
}
Very straight forward as well is deleting existing items, just pass in the query you’d use to search for an item, but using the SecItemDelete
method. And voila, it’s gone.
One more thing…
To make this little wrapper slightly more friendly we’re going to add some API sugar on top, using a custom subscript.
subscript(key: KeychainKey) -> String? {
get {
return get(stringForKey: key)
} set {
guard let value = newValue else {
del(valueForKey: key)
return
}
set(value, forKey: key)
}
}
We just want to be able to set a value on our KeychainWrapper
instance and have it persisted inside the keychain e.g.:
keychain["username"] = password
That’s about it for a fully functional, yet slim implementation of an iOS keychain. The system keychain is more powerful than a single blogpost could do justice for, but I hope you’ve got an idea of how to write your own, tailored to your needs, implementation now.
I’ve created a sample project here which contains the KeychainWrapper
as well as a simple iOS App to try it out.
I would love to hear your opinion on this so please feel free to drop me a line on Mastodon or via Email.
Putting all the bits and pieces together
And here’s the full implementation of the KeychainWrapper
:
import Foundation
typealias KeychainService = String
typealias KeychainKey = String
class KeychainWrapper {
let service: String
init(service: String) {
self.service = service
}
private func keychainQuery(withService service: KeychainService, forKey key: KeychainKey) -> [String: Any] {
return [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
kSecAttrSynchronizable as String: kCFBooleanTrue!
]
}
@discardableResult
func set(_ string: String, forKey key: KeychainKey) -> Bool {
var query = keychainQuery(withService: service, forKey: key)
guard let data = string.data(using: .utf8) else {
return false
}
guard SecItemCopyMatching(query as CFDictionary, nil) == noErr else {
query[kSecValueData as String] = data
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecSuccess
}
return SecItemUpdate(
query as CFDictionary,
NSDictionary(dictionary: [kSecValueData: data])
) == errSecSuccess
}
func get(stringForKey key: KeychainKey) -> String? {
var query = keychainQuery(withService: service, forKey: key)
query[kSecReturnData as String] = kCFBooleanTrue
query[kSecReturnAttributes as String] = kCFBooleanTrue
var result: CFTypeRef?
guard SecItemCopyMatching(query as CFDictionary, &result) == noErr else {
return nil
}
guard
let dictionary = result as? [String: Any],
let data = dictionary[kSecValueData as String] as? Data
else {
return nil
}
return String(data: data, encoding: .utf8)
}
@discardableResult
func del(valueForKey key: KeychainKey) -> Bool {
let query = keychainQuery(withService: service, forKey: key)
return SecItemDelete(query as CFDictionary) == noErr
}
subscript(key: KeychainKey) -> String? {
get {
return get(stringForKey: key)
} set {
guard let value = newValue else {
del(valueForKey: key)
return
}
set(value, forKey: key)
}
}
}
I would love to hear your opinion on this so please feel free to drop me a line on Mastodon or via Email.
Cheers, Marcus