Last updated on: May 27, 2023
Keychain is an encrypted database. You can store user’s sensitive information, such as credit card numbers, authentication tokens, passwords, or even short notes, without the fear of someone else from outside will have access to them.
In iOS, there’s only one keychain (which includes your iCloud keychain). The keychain is automatically locked/unlocked when the user locks/unlocks their device.
To “talk” to the database (add, retrieve, update, delete), we are doing it through the Keychain Services API.
Keychain Services is a part of Apple’s Security Framework
Keychain Services contains 2 main parts:
- SecKeychain: The encrypted database
- SecKeychainItem: Items you insert in the database
Contents
How it works
When you store data in the database, you need to “say” what you are going to save.
For example, if we want to save the user’s password, we can “say” it using the item class kSecClassGenericPassword.
There are 5 types of item class:
- kSecClassGenericPassword: for generic passwords
- kSecClassInternetPassword: for internet passwords
- kSecClassCertificate: for certificates
- kSecClassKey: for cryptographic keys
- kSecClassIdentity: for identities
If you click one of the links above, you’ll see a page like the following:
Under the Discussion section, this list is the list of attributes that this class supports.
Each item class has its own attributes.
We use these attributes as an identifiers or metadata for the data we’re going to save.
In our example (password), we’re going to use the kSecAttrAccount attribute and use the user’s username as an identifier.
And at the end, you’re “saying” the value that you want to store (e.g., password) using the kSecValueData
Saving Item
When you add an item in the database, keychain services encrypt the data first and then wraps it together with the attributes.
To save an item, we use the function: func SecItemAdd(_ attributes: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus
import Security
// Set username and password
let username = "john"
let password = "1234".data(using: .utf8)!
// Set attributes
let attributes: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username,
kSecValueData as String: password,
]
// Add user
if SecItemAdd(attributes as CFDictionary, nil) == noErr {
print("User saved successfully in the keychain")
} else {
print("Something went wrong trying to save the user in the keychain")
}
Code language: Swift (swift)
If you want, you can tag your item using the kSecAttrLabel attribute to find it easier later on.
Retrieving Item
We can find an item by searching the attributes we added with the data when we stored it in the database.
In this example, we are retrieving the item by checking if the user with the username “john” exists in the keychain. If it exists, we decrypt the results and print them.
Don’t forget that you need to set the class of the item you’re retrieving the same as the item class you used when you saved it (e.g., kSecGenericPassword)
import Security
// Set username of the user you want to find
let username = "john"
// Set query
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnAttributes as String: true,
kSecReturnData as String: true,
]
var item: CFTypeRef?
// Check if user exists in the keychain
if SecItemCopyMatching(query as CFDictionary, &item) == noErr {
// Extract result
if let existingItem = item as? [String: Any],
let username = existingItem[kSecAttrAccount as String] as? String,
let passwordData = existingItem[kSecValueData as String] as? Data,
let password = String(data: passwordData, encoding: .utf8)
{
print(username)
print(password)
}
} else {
print("Something went wrong trying to find the user in the keychain")
}
Code language: Swift (swift)
Updating Item
To update an item, you query the items, and if you find it, you can change the data using the function: func SecItemUpdate(_ query: CFDictionary, _ attributesToUpdate: CFDictionary) -> OSStatus
import Security
// Set username and new password
let username = "john"
let newPassword = "5678".data(using: .utf8)!
// Set query
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username,
]
// Set attributes for the new password
let attributes: [String: Any] = [kSecValueData as String: newPassword]
// Find user and update
if SecItemUpdate(query as CFDictionary, attributes as CFDictionary) == noErr {
print("Password has changed")
} else {
print("Something went wrong trying to update the password")
}
Code language: Swift (swift)
Deleting Item
Deleting an item works the same as updating it but without setting the attributes and with the use of the function: func SecItemDelete(_ query: CFDictionary) -> OSStatus
import Security
// Set username
let username = "john"
// Set query
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username,
]
// Find user and delete
if SecItemDelete(query as CFDictionary) == noErr {
print("User removed successfully from the keychain")
} else {
print("Something went wrong trying to remove the user from the keychain")
}
Code language: Swift (swift)
You can find the final project here
If you have any questions, please feel free to leave a comment below