How to Use Keychain in Swift for Secure Data Storage
Use the
Keychain Services API in Swift to securely store sensitive data like passwords. You create queries with attributes such as kSecClass and kSecAttrAccount to add, update, or retrieve items from the keychain.Syntax
The Keychain API uses dictionaries with specific keys to define what data to store or retrieve. Common keys include kSecClass (type of item), kSecAttrAccount (identifier), and kSecValueData (the data to save).
Use SecItemAdd to add, SecItemCopyMatching to read, and SecItemUpdate to modify keychain items.
swift
let addQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: "user@example.com", kSecValueData as String: "password123".data(using: .utf8)! ] let status = SecItemAdd(addQuery as CFDictionary, nil)
Output
status == errSecSuccess if item added successfully
Example
This example shows how to save a password to the keychain, then read it back securely.
swift
import Foundation func savePassword(account: String, password: String) -> Bool { let data = password.data(using: .utf8)! let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: data ] SecItemDelete(query as CFDictionary) // Remove old item if exists let status = SecItemAdd(query as CFDictionary, nil) return status == errSecSuccess } func getPassword(account: String) -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, 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, let data = item as? Data else { return nil } return String(data: data, encoding: .utf8) } // Usage let saved = savePassword(account: "user@example.com", password: "password123") let retrieved = getPassword(account: "user@example.com") print("Saved: \(saved), Retrieved: \(retrieved ?? "none")")
Output
Saved: true, Retrieved: password123
Common Pitfalls
- Not deleting existing keychain items before adding new ones causes
errSecDuplicateItemerrors. - Forgetting to convert strings to
Datawhen saving or back toStringwhen reading. - Not setting
kSecReturnDatatotruewhen reading, so no data is returned. - Ignoring the status code returned by keychain functions, which indicates success or failure.
swift
/* Wrong: Adding duplicate item without deleting */ let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: "user@example.com", kSecValueData as String: "pass".data(using: .utf8)! ] let status1 = SecItemAdd(query as CFDictionary, nil) let status2 = SecItemAdd(query as CFDictionary, nil) // Fails with errSecDuplicateItem /* Right: Delete before add */ SecItemDelete(query as CFDictionary) let status3 = SecItemAdd(query as CFDictionary, nil)
Quick Reference
Keychain common keys and their purpose:
| Key | Description |
|---|---|
| kSecClass | Type of item (e.g., generic password) |
| kSecAttrAccount | Unique identifier for the item |
| kSecValueData | Data to store (password, token) |
| kSecReturnData | Request to return data when reading |
| kSecMatchLimit | Limit number of results (usually one) |
Key Takeaways
Use Keychain Services API with dictionary queries to securely store and retrieve data.
Always convert strings to Data when saving and back to String when reading.
Delete existing items before adding to avoid duplicate errors.
Check the status code returned by keychain functions to handle errors.
Set kSecReturnData to true when reading to get the stored data.