philiprehberger/swift-keychain-kit
Modern, type-safe Keychain wrapper with Codable, biometric auth, and async/await
Requirements
- Swift >= 6.0
- macOS 13+ / iOS 16+ / tvOS 16+ / watchOS 9+
Installation
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/philiprehberger/swift-keychain-kit.git", from: "0.2.0")
]Then add "KeychainKit" to your target dependencies:
.target(name: "YourTarget", dependencies: [
.product(name: "KeychainKit", package: "swift-keychain-kit")
])Usage
import KeychainKit
let keychain = Keychain(service: "com.myapp")
try keychain.set("secret-token", for: "api-key")
let token = try keychain.string(for: "api-key") // => "secret-token"Codable Storage
Store any Codable type as JSON in the Keychain:
struct Credentials: Codable, Sendable {
let username: String
let token: String
}
let creds = Credentials(username: "alice", token: "abc123")
try keychain.set(creds, for: "credentials")
let restored = try keychain.object(for: "credentials", as: Credentials.self)
// => Credentials(username: "alice", token: "abc123")Biometric Authentication
Protect items with Face ID or Touch ID:
try keychain.setWithBiometric(
creds,
for: "secure-creds",
policy: .biometricAny,
prompt: "Authenticate to access credentials"
)
let secured = try await keychain.objectWithBiometric(
for: "secure-creds",
as: Credentials.self
)Access Control
Control when items are accessible:
try keychain.set("value", for: "key", access: .afterFirstUnlock)
try keychain.set("value", for: "key", access: .whenPasscodeSet)Key Rotation
Rotate keys atomically:
try KeyRotation.rotate(in: keychain, from: "v1.token", to: "v2.token")
// Batch rotation with prefix
let count = try KeyRotation.rotateAll(
in: keychain,
matchingPrefix: "v1."
) { $0.replacingOccurrences(of: "v1.", with: "v2.") }Expiration / TTL
Store values that expire after a given time:
try keychain.set("temp-token", for: "session", expiresIn: 3600) // 1 hour
try keychain.isExpired("session") // false
// Clean up all expired items
let removed = try keychain.cleanExpired()Biometric String & Data
Retrieve strings and raw data with biometric protection:
let token = try await keychain.stringWithBiometric(for: "api-key")
let cert = try await keychain.dataWithBiometric(for: "certificate")Key Management
try keychain.contains("api-key") // => true
try keychain.delete("api-key")
try keychain.deleteAll()
let keys = try keychain.allKeys() // => ["credentials", "secure-creds"]API
Keychain
| Method | Description | |--------|-------------| | Keychain(service:) | Create a Keychain scoped to a service identifier | | .set(:for:access:) | Store a String, Data, Bool, or Codable value | | .string(for:) | Retrieve a string | | .data(for:) | Retrieve raw data | | .object(for:as:) | Retrieve a Codable value | | .bool(for:) | Retrieve a boolean | | .contains(:) | Check if a key exists | | .delete(:) | Delete a single key | | .deleteAll() | Delete all items for this service | | .allKeys() | List all stored keys | | .set(:for:expiresIn:access:) | Store a value with time-to-live | | .isExpired(:) | Check if a stored item has expired | | .cleanExpired() | Remove all expired items, return count | | .setWithBiometric(:for:policy:prompt:) | Store with biometric protection | | .objectWithBiometric(for:as:prompt:) | Retrieve with biometric auth (async) | | .stringWithBiometric(for:prompt:) | Retrieve biometric-protected string (async) | | .dataWithBiometric(for:prompt:) | Retrieve biometric-protected data (async) |
KeyRotation
| Method | Description | |--------|-------------| | .rotate(in:from:to:) | Move a value from one key to another | | .rotateAll(in:matchingPrefix:transform:) | Rotate all keys matching a prefix |
AccessLevel
| Value | Description | |-------|-------------| | .whenUnlocked | Accessible when device is unlocked (default) | | .afterFirstUnlock | Accessible after first unlock since boot | | .whenPasscodeSet | Only when device has a passcode |
BiometricPolicy
| Value | Description | |-------|-------------| | .devicePasscode | Require device passcode | | .biometricAny | Require Face ID or Touch ID | | .biometricCurrentSet | Require currently enrolled biometric |
KeychainError
| Case | Description | |------|-------------| | .itemNotFound | Requested item not found | | .duplicateItem | Item already exists | | .authenticationFailed | Biometric or passcode auth failed | | .encodingFailed(String) | Failed to encode value | | .decodingFailed(String) | Failed to decode value | | .accessDenied | Access denied | | .unexpectedStatus(Int32) | Unexpected Security framework status |
Development
swift build
swift testSupport
If you find this project useful:
β Star the repo
π Report issues
π‘ Suggest features
β€οΈ Sponsor development
π» GitHub Profile
π LinkedIn Profile
License
Package Metadata
Repository: philiprehberger/swift-keychain-kit
Default branch: main
README: README.md