Contents

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 test

Support

If you find this project useful:

⭐ Star the repo

πŸ› Report issues

πŸ’‘ Suggest features

❀️ Sponsor development

🌐 All Open Source Projects

πŸ’» GitHub Profile

πŸ”— LinkedIn Profile

License

MIT

Package Metadata

Repository: philiprehberger/swift-keychain-kit

Default branch: main

README: README.md