Saving Passwords in the Keychain in Swift
The Keychain is the place to store small amounts of data securely, such as passwords and API tokens. In this article you’ll learn how to work with the Keychain in iOS and Mac apps using the Keychain Services framework.
Parts of a Keychain Action
There are four common actions to perform on keychain items: add an item, update an item, read an item, and delete an item. To perform a keychain action, create a query and run a Keychain Services function.
Query
A query tells Keychain Services what you want to do. A query is a Swift dictionary that you must cast to CFDictionary
.
Every query must include an entry with the key kSecClass
, which specifies the kind of item to add, update, read, or delete. There are the following Keychain item types:
- Generic password,
kSecClassGenericPassword
- Internet password,
kSecClassInternetPassword
- Certificate,
kSecClassCertificate
- Cryptographic key item,
kSecClassKey
- Identity item,
kSecClassIdentity
I’m going to focus on generic passwords in this article, as that’s what most apps use. You’re not limited to passwords when using generic passwords. I was able to use generic passwords to store OAuth tokens in the Keychain.
The query keys you can use depend on the class. You can find a list of possible keys in the following section of Apple’s documentation:
Click the link for a class to see the available items. You can also read the documentation in Xcode by choosing Help > Developer Documentation.
Two common item attributes for generic passwords are services, kSecAttrService
, and accounts, kSecAttrAccount
.
The value you supply for the service is the text that appears for the item in the Keychain Access app on Mac. Make sure the text clearly shows the keychain item is part of your app. A generic value like password
will be hard to find in the Keychain Access app.
The value for the account is the name of the password’s account. If you’re writing a Mastodon client, Mastodon
would be a good value for the account.
Keychain Services Functions
Call the following functions to work with the Keychain for passwords:
SecItemAdd
to add an item to the KeychainSecItemUpdate
to update an existing Keychain itemSecItemCopyMatching
to read an item from the KeychainSecItemDelete
to delete an item from the Keychain
Adding a Keychain Item
Start by importing the Authentication Services framework. The Keychain Services API is in the Authentication Services framework.
import AuthenticationServices
You should also create a class for the Keychain functions.
Let’s start by writing a function to add an item to the Keychain.
func save(_ data: Data, service: String, account: String) {
let query = [
kSecValueData: data,
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account
] as CFDictionary
let saveStatus = SecItemAdd(query, nil)
if saveStatus != errSecSuccess {
print("Error: \(saveStatus)")
}
if saveStatus == errSecDuplicateItem {
update(data, service: service, account: account)
}
}
The function starts by building a query. Tell Keychain Services that you’re adding a generic password. Supply the data, service, and account. The data is what you want to save in the Keychain.
After building the query, call the function SecAddItem
to add the item to the Keychain. If the item is added to the Keychain successfully, SecAddItem
returns the value errSecSuccess
.
If the item already exists in the Keychain, SecAddItem
returns the value errSecDuplicateItem
. In this case update the existing item, which I cover next.
Updating an Existing Keychain Item
Let’s look at the code to update a keychain item.
func update(_ data: Data, service: String, account: String) {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account
] as CFDictionary
let updatedData = [kSecValueData: data] as CFDictionary
SecItemUpdate(query, updatedData)
}
Notice that the update
function doesn’t include the data in the query. The code creates another dictionary for the data and passes it as an argument to the function SecItemUpdate
.
Reading an Item from the Keychain
Saving items to the Keychain isn’t going to help unless your app can read the keychain items.
func read(service: String, account: String) -> Data? {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account,
kSecReturnData: true
] as CFDictionary
var result: AnyObject?
SecItemCopyMatching(query, &result)
return result as? Data
}
The big difference in the query is the kSecReturnData
key, which tells Keychain Services you want to return data from the function call. The function SecItemCopyMatching
returns its results as type AnyObject?
. You saved the item as Data
so you should return a Data
object. If there is no matching item in the Keychain, SecItemCopyMatching
returns nil.
Deleting an Item from the Keychain
The last major task to perform on Keychain items is to delete items from the Keychain.
func delete(service: String, account: String) {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account
] as CFDictionary
SecItemDelete(query)
}
The query for deleting an item is the simplest one. Supply the class, service, and account. Call SecItemDelete
to delete the item from the Keychain.