Keychain is a secure credential storage in iOS, watchOS, macOS, and tvOS that manages passwords, keys, and certificates at the hardware level. Unlike UserDefaults, data in Keychain is encrypted and isolated per application sandbox. According to Apple Developer Documentation, Keychain uses Secure Enclave mechanisms and AES-256 hardware encryption to protect sensitive information.
Key Takeaways
Keychain is a secure database of the Apple operating system designed for safe storage of passwords, cryptographic keys, certificates, and other sensitive data. Keychain first appeared in Mac OS 8.6 in 1999, and on iOS — from the very first SDK version.
Keychain solves a fundamental problem of mobile development: storing secrets (tokens, passwords, encryption keys) in an unsecured file system. Unlike UserDefaults or SQLite, data in Keychain is stored encrypted and is automatically encrypted when written to disk.
Each iOS application runs in an isolated sandbox and has access only to its own keychain. However, the developer can configure shared Keychain access between applications of the same developer through keychain access groups. The user's iCloud Keychain additionally synchronizes data between devices through end-to-end encryption.
According to Apple Security White Paper (2025), Keychain uses AES-256 hardware encryption with a key tied to the unique device identifier (UID), making data inaccessible when extracted from the device.
Keychain functions as a layered service: the application accesses the Security framework, which communicates with the securityd daemon that manages the encrypted SQLite Keychain database. Each record is encrypted with a separate key, and the keys themselves are protected by the Secure Enclave hardware module on devices with A7 chip and newer.
The Security framework provides a C interface for working with Keychain Services. Main functions: SecItemAdd (add), SecItemCopyMatching (search), SecItemUpdate (update), and SecItemDelete (delete). Each operation accepts a CFDictionaryRef attribute dictionary describing the request type.
Data is classified by classes: kSecClassGenericPassword (arbitrary password), kSecClassInternetPassword (internet service password), kSecClassCertificate (certificate), kSecClassKey (cryptographic key), and kSecClassIdentity (key and certificate pair).
When saving a password, the application creates a dictionary with attributes: service, account, access level, label. Security framework passes the data to the securityd daemon, which encrypts the record with a device-bound key and stores it in the Keychain SQLite. Retrieval works similarly: query by attributes, search, decryption, and data return.
An important feature: when an application is deleted, iOS automatically clears Keychain records created by that application (since iOS 10.3+). When reinstalling the application, Keychain data from the previous installation is unavailable unless iCloud Keychain was used.
import Security
func savePassword(service: String, account: String, password: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecValueData as String: password,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
SecItemAdd(query as CFDictionary, nil)
}
Keychain supports five main data classes, each with its own set of attributes and application scope. The choice of class determines which search keys are available and how data is encrypted.
The kSecClassGenericPassword and kSecClassInternetPassword classes are designed for storing string passwords. The Internet version additionally stores server data: domain, protocol, port, and authentication path. GenericPassword is used for storing any passwords without server binding.
When saving an internet password, you can specify kSecAttrServer (domain), kSecAttrProtocol (HTTPS, FTP, etc.), kSecAttrPort, and kSecAttrPath. This allows precise identification of the record during automatic filling via AutoFill.
The kSecClassKey class stores symmetric (AES) and asymmetric (RSA, EC) keys. The kSecClassCertificate class stores X.509 certificates. The kSecClassIdentity class combines a private key and its corresponding certificate into one record.
For asymmetric keys, Keychain supports attributes: kSecAttrKeyType (RSA, EC), kSecAttrKeySizeInBits (2048, 256), kSecAttrIsPermanent (whether to save to Keychain). For certificates: kSecAttrLabel (human-readable name), kSecAttrSubject (owner DN), kSecAttrIssuer (issuer DN).
| Keychain Class | Purpose | Data Type |
|---|---|---|
| kSecClassGenericPassword | App password | Data (NSString) |
| kSecClassInternetPassword | Server password | Data + server attributes |
| kSecClassCertificate | X.509 certificate | SecCertificate |
| kSecClassKey | Cryptographic key | SecKey |
| kSecClassIdentity | Key + certificate | SecIdentity |
The main interface for interacting with Keychain in native iOS code is the Security framework functions. For Swift and Objective-C, a single C interface is available, wrapped in convenient functions.
The SecItemAdd function adds a new item to the Keychain. It takes a dictionary with class, data, and attributes as input. Returns errSecSuccess (0) status or an error code. The SecItemCopyMatching function performs a search by attributes and returns the found data.
For searching, the kSecReturnData flag is used — if true, the function returns the record contents. The kSecMatchLimit flag determines the number of results: kSecMatchLimitOne (one) or kSecMatchLimitAll (all).
func readPassword(service: String, account: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else { return nil }
return item as? Data
}
SecItemUpdate allows changing attributes or data of an existing record. SecItemDelete deletes all records matching the passed dictionary. When deleting, it is important to specify enough attributes to avoid accidentally deleting other records in the shared keychain.
func deletePassword(service: String, account: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account
]
SecItemDelete(query as CFDictionary)
}
Each Keychain record has the kSecAttrAccessible attribute, which determines when the data is available for reading. This is critically important for security: the wrong access level can make data vulnerable or, conversely, unavailable when needed.
kSecAttrAccessibleWhenUnlocked — data is available only when the device is unlocked. kSecAttrAccessibleAfterFirstUnlock — available after the first unlock after reboot. kSecAttrAccessibleAlways — always available (not recommended). The ThisDeviceOnly postfix prevents data transfer through backup.
On devices with Secure Enclave, the Keychain encryption key can be additionally protected by biometrics: kSecAccessControlBiometryCurrentSet or kSecAccessControlUserPresence. This adds a Face ID or Touch ID requirement for each read operation.
iCloud Keychain synchronizes records between Apple devices through end-to-end encryption. Apple does not have access to decrypted data. To enable synchronization, add the kSecAttrSynchronizable attribute with the value kCFBooleanTrue.
Important: synchronized records cannot contain the ThisDeviceOnly attribute, as they must be available on other devices. If all devices are lost, iCloud Keychain can be restored using a recovery code or iCloud recovery key.
For authentication tokens, use kSecAttrAccessibleWhenUnlockedThisDeviceOnly — data is only available when the device is unlocked and is not copied to backups. For certificates that need to be available for background tasks, use kSecAttrAccessibleAfterFirstUnlock. Never use kSecAttrAccessibleAlways for sensitive data.
It is recommended to always add the ThisDeviceOnly postfix for data that does not require synchronization. This prevents accidental copying of secrets to iCloud or iTunes backup, where they could be extracted by analysis tools. For critical data, combine accessibility with kSecAccessControlUserPresence — this adds a biometric or passcode requirement for record decryption.
When using iCloud Keychain, keep in mind that synchronized records are available on all user devices: if an attacker gains access to one device, data could be compromised on all. For isolated secrets, use ThisDeviceOnly.
Frequently Asked Questions
UserDefaults stores data in plain text in a .plist file, accessible during IPA analysis. Keychain encrypts each record separately, uses hardware encryption, and supports access levels, biometrics, and synchronization via iCloud.
On each launch of the iOS simulator, Keychain is cleared. On a real device, delete the app — iOS will remove all Keychain records of that developer. For selective clearing, call SecItemDelete with the appropriate attributes.
Yes, through keychain access groups. Applications must have the same team ID in the provisioning profile and entitlements with the same keychain-access-groups. The developer configures shared access in Xcode Capabilities.
Apple does not document a strict limit, but in practice it is recommended not to exceed 1–4 KB per record. For larger volumes, use file encryption and store the key in Keychain, while the data itself goes in Documents.
On a jailbroken device, Keychain protection is reduced because the attacker gains root access and can read the Keychain SQLite directly. For critical data, use additional encryption with a key obtained from the server.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also