Keychain Basics
What is Keychain?
Keychain Services is Apple's secure storage system for sensitive data like passwords, tokens, and encryption keys. Data stored in Keychain is encrypted and persists across app reinstalls.
When to Use Keychain
Use Keychain for:
- Authentication tokens and session data
- API keys and secrets
- Passwords and credentials
- Encryption keys
- Any data that must survive app deletion
Do NOT use Keychain for:
- User preferences (use UserDefaults)
- Large data blobs (use file system)
- Temporary data
Keychain vs UserDefaults
- UserDefaults: Plaintext, not encrypted, deleted with app
- Keychain: Encrypted, survives app deletion, access-controlled
Basic Usage with SecItem
The SecItem API is the low-level interface:
import Security
// Add
let data = "secret-token".data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.app.auth",
kSecAttrAccount as String: "user-token",
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemAdd(query as CFDictionary, nil)
Access Control
Keychain items have accessibility levels:
- kSecAttrAccessibleWhenUnlocked: Only when device is unlocked
- kSecAttrAccessibleAfterFirstUnlock: After first unlock until restart
- kSecAttrAccessibleWhenUnlockedThisDeviceOnly: Unlocked, not backed up
- kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: After first unlock, not backed up
SecItem API
Reading Keychain Items
func load(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 result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let data = result as? Data else {
return nil
}
return data
}
Updating Keychain Items
func save(service: String, account: String, data: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account
]
let updateQuery: [String: Any] = [
kSecValueData as String: data
]
let status = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary)
if status == errSecItemNotFound {
var addQuery = query
addQuery[kSecValueData as String] = data
SecItemAdd(addQuery as CFDictionary, nil)
}
}
Deleting Keychain Items
func delete(service: String, account: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account
]
SecItemDelete(query as CFDictionary)
}
Error Handling
let status = SecItemAdd(query as CFDictionary, nil)
switch status {
case errSecSuccess:
print("Saved successfully")
case errSecDuplicateItem:
print("Item already exists")
case errSecItemNotFound:
print("Item not found")
default:
print("Error: \(status)")
}
Keychain Wrapper
Building a Reusable Wrapper
Simplify keychain access with a wrapper class:
class KeychainWrapper {
private let service: String
init(service: String = Bundle.main.bundleIdentifier ?? "com.app") {
self.service = service
}
func save(_ value: String, forKey key: String) {
guard let data = value.data(using: .utf8) else { return }
delete(forKey: key)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
]
SecItemAdd(query as CFDictionary, nil)
}
func load(forKey key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let data = result as? Data else { return nil }
return String(data: data, encoding: .utf8)
}
func delete(forKey key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key
]
SecItemDelete(query as CFDictionary)
}
}
Usage
let keychain = KeychainWrapper()
// Save
keychain.save("my-api-token", forKey: "authToken")
// Load
if let token = keychain.load(forKey: "authToken") {
print("Token: \(token)")
}
// Delete
keychain.delete(forKey: "authToken")
Codable Keychain Storage
For storing Codable objects:
func saveCodable<T: Encodable>(_ value: T, forKey key: String) {
guard let data = try? JSONEncoder().encode(value) else { return }
// Store data in keychain
}
func loadCodable<T: Decodable>(forKey key: String) -> T? {
guard let data = loadData(forKey: key) else { return nil }
return try? JSONDecoder().decode(T.self, from: data)
}
Quiz
1. What is Keychain Services used for?
2. What function adds a new item to Keychain?
3. What does kSecAttrAccessibleAfterFirstUnlock mean?
4. How does Keychain data differ from UserDefaults?
Flashcards
Question
What is Keychain Services?
Click to reveal answer
Answer
Apple's encrypted storage for sensitive data like passwords, tokens, and keys. Data persists across app reinstalls.
Question
What is the difference between SecItemAdd and SecItemUpdate?
Click to reveal answer
Answer
SecItemAdd creates a new keychain item. SecItemUpdate modifies an existing item.
Question
When should you use Keychain vs UserDefaults?
Click to reveal answer
Answer
Keychain for sensitive data (tokens, passwords). UserDefaults for non-sensitive preferences.
Question
What does errSecDuplicateItem mean?
Click to reveal answer
Answer
The keychain item already exists. You must update instead of add, or delete first.
Revision Notes
Key Takeaways
- 1. Keychain provides encrypted storage for sensitive data that persists across app reinstalls
- 2. SecItem API uses dictionaries for add, read, update, and delete operations
- 3. Accessibility levels control when keychain data can be read
- 4. Wrap Keychain operations in a reusable class for clean API
- 5. Handle errSecDuplicateItem by updating instead of adding
Interview Tips
- • Explain when to use Keychain vs UserDefaults
- • Know the key SecItem functions and their parameters
- • Describe keychain accessibility levels and their security implications
- • Discuss keychain data migration when switching devices
Cheat Sheet
Keychain: Encrypted secure storage. SecItem API: Add, Copy, Update, Delete. kSecClass: GenericPassword, InternetPassword, Certificate, Key, Identity. kSecAttrAccessible: Controls when data is accessible. ErrSecDuplicateItem: Item exists, use update. Always handle SecItem status codes.