How does the Keychain work, how do you add, read and update an item, and what do accessibility classes and access groups do?
Predict first
Add, add again, read, update, then read the Keychain item. What are the statuses and values? (Run on macOS, where the same SecItem calls reach the login keychain.)
import Foundation
import Security
let base: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "demo.svc", kSecAttrAccount as String: "user"]
SecItemDelete(base as CFDictionary)
var add = base
add[kSecValueData as String] = Data("t1".utf8)
print(SecItemAdd(add as CFDictionary, nil))
print(SecItemAdd(add as CFDictionary, nil))
var q = base
q[kSecReturnData as String] = true
q[kSecMatchLimit as String] = kSecMatchLimitOne
var out: CFTypeRef?
SecItemCopyMatching(q as CFDictionary, &out)
print(String(data: out as! Data, encoding: .utf8)!)
print(SecItemUpdate(base as CFDictionary, [kSecValueData as String: Data("t2".utf8)] as CFDictionary))
out = nil
SecItemCopyMatching(q as CFDictionary, &out)
print(String(data: out as! Data, encoding: .utf8)!)
SecItemDelete(base as CFDictionary)
print(SecItemCopyMatching(q as CFDictionary, &out))
- 0, -25299, t1, 0, t2, -25300
- 0, 0, t1, 0, t2, 0
- 0, -25299, t1, -25300, t1, -25300
Mental model. The Keychain is a hotel safe with a clerk: you give a service name and an account (the key), you can add, read, change and remove, and you choose whether it opens only while the lobby is open (unlocked) or any time after opening hours begin (after first unlock).
The Keychain is an encrypted database managed by the system, accessed through the C SecItem API with dictionaries of attributes. An item belongs to a class (kSecClassGenericPassword for tokens and passwords, kSecClassInternetPassword, kSecClassKey, kSecClassCertificate, kSecClassIdentity). For a generic password the primary key is the combination of attributes you set: kSecAttrService plus kSecAttrAccount (and the access group). The calls: SecItemAdd (adds; returns errSecDuplicateItem, −25299, if an item with the same key exists), SecItemCopyMatching (reads, with kSecReturnData and kSecMatchLimit in the query; returns errSecItemNotFound, −25300, when nothing matches), SecItemUpdate (a query dictionary plus a dictionary of attributes to change) and SecItemDelete. Every call returns an OSStatus you must check; the idiom for “save” is add, and on duplicate update. Wrap it in a small type that throws, so no screen touches the C API.
Two attributes decide security. Accessibility (kSecAttrAccessible) says when the item can be read: WhenUnlocked (only while the device is unlocked), AfterFirstUnlock (from the first unlock after boot, which background work needs), and the …ThisDeviceOnly variants, which are never migrated to another device or restored from backup. The default for items you add without it is WhenUnlocked; choose AfterFirstUnlock for tokens a background task must read, and ThisDeviceOnly for secrets that should not move. Add access control (SecAccessControl) to require Face ID or the passcode on read. Access groups (kSecAttrAccessGroup, defined by the Keychain Sharing capability) let apps from the same team and extensions share items. Common pitfalls: items persist after uninstall; reads that fail while the device is locked in the background (errSecInteractionNotAllowed); a missing entitlement returns errSecMissingEntitlement (−34018) in an unsigned test binary, so test Keychain code in a real app target or a host app; the simulator’s keychain is separate and reset with the simulator; and use kSecUseDataProtectionKeychain on macOS for the iOS-style keychain. Never log values, and never store them in UserDefaults as a “cache”.
The first add succeeds, the second hits the duplicate error (−25299), update replaces the value, and after deletion the query reports not found (−25300).
Common traps
- Not handling
errSecDuplicateItem. A secondSecItemAddfails; update the existing item. - Choosing
WhenUnlockedfor a token a background fetch needs. The read fails while the phone is locked; useAfterFirstUnlock.
They might follow up with
- How do two apps share a Keychain item? The same access group through the Keychain Sharing capability.
- What does
ThisDeviceOnlyadd? The item never leaves the device, so it is not restored on a new phone.
Related questions
- 13.1.1 UserDefaults, Keychain, files, SQLite, Core Data or SwiftData: how do you choose, and what survives deletion…
- 13.5.1 What is the repository pattern, and why map storage models to domain models?
- 6.8.2 How do keychain access groups differ from app groups?
← UserDefaults, Keychain, files, SQLite, Core Data or SwiftData: how do…