Working with the Keychain in iOS
Learn how to securely store sensitive data using Apple’s Keychain Services API.
25 May 2026 · 6 min read
When developing iOS apps, we often need to persist sensitive user data such as passwords, authentication tokens, or cryptographic keys.
For this type of data, storing values in plain files or UserDefaults is usually not appropriate. Instead, iOS provides the Keychain Services API, which stores sensitive data in an encrypted and system-managed database.
In this article, we'll look at how the Keychain API works and how to save, retrieve, update, and delete keychain items in Swift.
Let's dive in.

A note on the API design
The Keychain Services API is a C-based API, which makes it feel less modern compared to many Swift APIs.
In practice, many apps wrap the API in a small Swift abstraction or use a third-party library to simplify common operations.
That said, understanding the underlying query structure is still valuable because most abstractions are ultimately built on top of the same API.
Understanding keychain queries
The Keychain Services API is based around queries.
A keychain query is a dictionary of key-value pairs that describes:
- the type of item we want to work with
- how the item can be identified
- additional configuration or access options
- the data itself
The same query structure is used for saving, reading, updating, and deleting items.
Saving keychain items
For example, to store a password in the keychain, we first create a query describing the item we want to save:
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,kSecAttrService: "com.app.service",kSecAttrAccount: username,kSecValueData: passwordData]
Let's break this down.
1. Defining the item type kSecClass: kSecClassGenericPassword
kSecClass defines the type of keychain item we want to store. In this case, we use kSecClassGenericPassword for storing a password. Other available types include:
- kSecClassInternetPassword to store Internet passwords.
- kSecClassCertificate to store certificates.
- kSecClassKey to store cryptographic keys.
- kSecClassIdentity to store identities.
The item type determines which attributes are available for the query.
2. Defining a namespace kSecAttrService: "com.app.service"
A string that namespaces your keychain items, typically the app's bundle identifier or a specific service name. This is important for avoiding collisions when multiple items share the same account name.
3. Defining item attributes kSecAttrAccount: username
Attributes are used to identify the item and provide additional metadata.
In this example, kSecAttrAccount stores the username associated with the password.
We can additionally provide extra query arguments to configure how the keychain item behaves. For example kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked, which controls when the item can be accessed. kSecAttrAccessibleWhenUnlocked ensures that the item is only accessible while the device is unlocked.
4. Providing the sensitive data kSecValueData: passwordData
The sensitive value itself is stored using kSecValueData. The value must be provided as Data.
Adding the item to the keychain
Once the query is ready, we can store the item using SecItemAdd:
let status = SecItemAdd(query as CFDictionary, nil)guard status == errSecSuccess else {throw KeychainError.errorWithStatus(status)}
The method returns a status code indicating whether the operation succeeded.
One error to watch our for is the error errSecDuplicateItem, which occurs when an item with the same attributes already exists. We can handle this by checking for that status and calling SecItemUpdate instead (see the update section below), or by deleting the old item before adding the new one.
Retrieving keychain items
To read a password from the keychain, we can create another query:
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,kSecAttrService: "com.app.service",kSecAttrAccount: username,kSecReturnData: true]
The key difference is kSecReturnData: true which tells the Keychain API that we want the stored value returned.
We can then retrieve the item using SecItemCopyMatching:
var result: CFTypeRef?let status = SecItemCopyMatching(query as CFDictionary, &result)guard status != errSecItemNotFound else {throw KeychainError.noPassword}guard status == errSecSuccess else {throw KeychainError.errorWithStatus(status)}guard let passwordData = result as? Data else {throw KeychainError.unexpectedPasswordData}
Once we have the data, we can convert it back into a String:
let password = String(data: passwordData, encoding: .utf8)
Updating keychain items
To update an existing item, we first define a query that identifies the item and then provide the updated values separately:
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,kSecAttrService: "com.app.service",kSecAttrAccount: username]let attributes: [CFString: Any] = [kSecValueData: newPasswordData]let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
The query identifies which item should be updated, while the attributes dictionary contains the new values.
Deleting keychain items
Deleting an item uses the same query-based approach:
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,kSecAttrService: "com.app.service",kSecAttrAccount: username]let status = SecItemDelete(query as CFDictionary)
If the operation succeeds, the item is permanently removed from the keychain.



Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips



