Motivation
Need to store sensitive information securely? You might know that UserDefaults
and CoreData
aren't recommended for storing sensitive data like passwords due to their vulnerability to security threats.
Enter the Keychain: Apple’s encrypted database designed specifically for storing sensitive user data securely on your device.
On iOS, each app has its own isolated keychain space. When you develop an app, you can only access your app’s keychain items or those explicitly shared within an app group.
The Keychain isn’t just for passwords — you can store various types of sensitive data including secrets, encryption keys, credit card information, and secure notes.
In this article, we’ll explore the Keychain Services API and show you how to implement secure storage for sensitive data like passwords and secrets.
Looking for a simple solution? I’ve created a key-value wrapper framework for Keychain called KeychainStorage.
You can integrate it using Swift Package Manager or by manually adding the files to your project.
Security framework
To work with the Keychain, you’ll need to import the Security framework. This framework provides essential security features including user authentication, secure data storage, code signing services, and low-level cryptographic functions.
Storing data
Here’s a basic example of storing data in the Keychain:
// Create a dictionary to store the keychain query parameters
var queryDictionary: [String: Any] = [:]
// Specify the service identifier for this keychain item
queryDictionary[kSecAttrService as String] = service
// Set the item class to generic password
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String
// Add the actual data to be stored
queryDictionary[kSecValueData as String] = data
// Set a unique identifier (key) for this keychain item
queryDictionary[kSecAttrAccount as String] = key
// Add the item to the keychain and get the status
// Status will be noErr (0) if successful
let status = SecItemAdd(queryDictionary as CFDictionary, nil)
When storing data in the Keychain, it’s packaged as a keychain item with specific attributes that control its accessibility and enable later retrieval.
Let’s break down the process of storing a password in the Keychain.
Transform the Value to Data
Before storing a value in the Keychain, we need to convert it to a Data
object. Here's how we transform a password string:
// Get the password string from a text field
let password = passwordTextField.text!
// Convert the string to Data using UTF-8 encoding
let data = password.data(using: .utf8)!
Note: In production code, you should avoid force unwrapping (!
) and instead handle these conversions safely:
guard let password = passwordTextField.text,
let data = password.data(using: .utf8) else {
// Handle the error case
return
}
The Keychain requires data to be stored as Data
objects because it's a more secure way to handle sensitive information, and it allows for storing various types of data beyond just strings (like encryption keys or certificates).
Define the Query Attributes
In this example, the following attributes are used:
kSecAttrService
: the same service used for storing the passwordkSecClass
: using generic password itemskSecAttrAccount
: the account name associated with the itemkSecMatchLimit
: set to eitherkSecMatchLimitOne
(returns exactly one item) orkSecMatchLimitAll
(returns an unlimited number of items)kSecReturnData
: if set totrue
, the item data will be returnedkSecReturnAttributes
: if set totrue
, the item's attributes will be returned
Retrieve the Value
To retrieve the value, we use the SecItemCopyMatching
function.
The result object is an optional UnsafeMutablePointer<CFTypeRef?>
which is cast to a Swift dictionary. The actual data is retrieved from the value associated with the kSecValueData
key.
UnsafeMutablePointer
is a struct with a generic type Pointee
. It accesses data of the Pointee
type in memory (in this case, CFTypeRef
). Since we don't have automatic memory management with this type, it's crucial to be cautious when using it.
CFTypeRef
gives a generic reference to any Core Foundation object:
// Two equivalent definitions of CFTypeRef
typealias CFTypeRef = AnyObject
// or
typedef const CF_BRIDGED_TYPE(id) void * CFTypeRef;
Deleting Data
To delete an item from the Keychain, use this code:
// Create a dictionary for the query parameters
var queryDictionary: [String: Any] = [:]
// Set the service identifier
queryDictionary[kSecAttrService as String] = service
// Specify we're looking for a generic password type
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String
// Set the key to find the specific item to delete
queryDictionary[kSecAttrAccount as String] = key
// Attempt to delete the item from the keychain
let status = SecItemDelete(queryDictionary as CFDictionary)
// Check for errors:
// - errSecItemNotFound: item doesn't exist (not an error in this case)
// - errSecSuccess: deletion successful
// - anything else: throw an error
if status != errSecItemNotFound && status != errSecSuccess {
throw KeychainStorageError.unhandledError(status)
}
The SecItemDelete
function removes all items that match the specified query criteria from the Keychain.
Conclusions
We’ve explored the basic operations that can be performed with the Keychain Services API, including storing, retrieving, and deleting passwords. For a complete implementation of these concepts, check out my open-source project on GitHub.
References
- Apple Documentation — Security Framework, Keychain Services
- Apple Documentation — Security Framework, Keychain Items
- Apple Documentation — Security Framework, Using the Keychain to Manage User Secrets
- Here you can find what a
OSStatus
number represents: (or check the internal documentation). - Question from StackOverflow, what makes a keychain item unique (in iOS)?