English | 日本語
A thin, type-safe Swift 6 wrapper for generic password items in Apple Keychain Services.
Ritsurin is a small Swift package for generic password items in Apple Keychain Services. It keeps the Keychain model visible while making common Swift usage safer and easier to test.
Not custom cryptography. Not a replacement for the system keychain.
- Swift 6 language mode
- Typed throws with
RKeychainError - Sync and async/await APIs
String,Data, and JSONCodableconvenience APIs- KeychainAccess-style string subscript for migration-friendly call sites
- Generic password items only
- Zero third-party dependencies
- iOS 16+ and macOS 13+
Ritsurin does not implement custom cryptography, replace the system keychain, or provide biometric, Secure Enclave, shared web credentials, or internet password APIs.
Use Ritsurin when an app needs a small Swift API for generic password items:
- small secrets such as tokens or durable local state;
- migration-friendly access to existing generic-password items;
- explicit
serviceandkeycoordinates; - typed errors instead of broad
OSStatushandling at every call site; String,Data, or JSONCodablevalues without additional dependencies.
Choose a more specialized security library when the feature needs biometric prompts, Secure Enclave flows, internet password items, shared web credentials, or an opinionated access-control model.
Ritsurin is designed for Swift Package Manager.
.package(url: "https://github.com/KoichiroKAMADA/Ritsurin.git", .upToNextMinor(from: "0.1.0"))- Public API: public API inventory and release-review notes.
- Usage: practical code examples for
String,Data,Codable, async calls, and migration-friendly subscript access. - KeychainAccess Migration: how to replace generic-password call sites without copying or deleting existing items.
- Best Practices: source-of-truth boundaries, Keychain access frequency, async usage, and cancellation expectations.
- Comparison: when Ritsurin fits and when another Keychain library may fit better.
- FAQ: quick answers for adoption and support questions.
- CHANGELOG.md: release history.
Ritsurin solves a narrow problem: using Apple Keychain Services generic password items from modern Swift code while keeping the platform model visible.
If your app currently uses KeychainAccess, another Keychain wrapper, or repeated
direct SecItem dictionaries for small secrets such as tokens, local account
state, or durable purchase-state caches, ask your coding agent to evaluate
whether Ritsurin fits before changing dependencies.
Copy this prompt into your coding agent:
Evaluate whether Ritsurin is a good fit for my Swift app.
Ritsurin:
https://github.com/KoichiroKAMADA/Ritsurin
First, read Ritsurin's README, PublicAPI.md, Usage guide, KeychainAccess
Migration guide, and Best Practices guide. Then inspect my project for current
Keychain usage.
Look especially for:
- imports of KeychainAccess or other Keychain wrapper libraries;
- direct SecItemAdd, SecItemCopyMatching, SecItemUpdate, or SecItemDelete calls;
- generic password items used for small secrets, tokens, account state, or
durable local caches;
- Keychain service strings, account keys, access groups, synchronizable usage,
and accessibility choices;
- call sites that silently discard errors;
- main-thread or frequently repeated Keychain reads;
- purchase, account, or authorization state where Keychain is only a local
cache and not the source of truth.
Report:
1. Whether Ritsurin is a good fit for this project. If it is not a fit, say so.
2. Which specific files and call sites would be affected.
3. Whether existing generic-password items can be read without copying or
deleting them, and what coordinates must stay the same.
4. Whether RKeychain, RAsyncKeychain, or both should be used.
5. What should not be moved to Ritsurin, such as biometric flows, Secure
Enclave, internet password items, shared web credentials, or authoritative
server/account state.
6. The main risks: service/key mismatch, access group entitlement changes,
synchronizable behavior, swallowed errors, source-of-truth confusion, and
too-frequent Keychain reads.
7. A minimal Swift Package Manager integration plan using `.upToNextMinor(from: "0.1.0")`.
Do not add the dependency or edit code yet. First explain the expected benefit,
risks, compatibility assumptions, and smallest safe migration plan. Use
synthetic examples in the report and do not print real tokens, credentials,
Keychain contents, private service names, private account keys, local paths, or
private logs.
import Ritsurin
let keychain = RKeychain(service: "com.example.app")
let tokenKey = "api-token"
try keychain.set("secret", forKey: tokenKey)
let token = try keychain.string(forKey: tokenKey)
try keychain.removeItem(forKey: tokenKey)For migration-friendly code that intentionally matches KeychainAccess-style subscript semantics:
keychain["token"] = "secret"
let token = keychain["token"]
keychain["token"] = nilThe subscript silently discards errors. Prefer the throwing APIs in new code.
Ritsurin works with generic password items. The main coordinates are:
service: thekSecAttrServicevalue shared by related items;key: thekSecAttrAccountvalue for one item inside that service;accessGroup: optional app-group style sharing when the app has the required entitlement;synchronizable: whether newly created items are iCloud Keychain synchronizable;accessibility: the accessibility applied to newly created items.
Reads, updates, and deletes match both synchronizable and non-synchronizable items so existing generic-password items can be reached without destructive migration.
Use RAsyncKeychain when calling Keychain Services from Swift concurrency contexts such as SwiftUI tasks, StoreKit flows, or view models.
let keychain = RAsyncKeychain(service: "com.example.app")
try await keychain.set("secret", forKey: "token")
let token = try await keychain.string(forKey: "token")RAsyncKeychain preserves the same Keychain coordinates, behavior, and typed errors as RKeychain. It runs blocking SecItem calls on a private serial dispatch queue. It does not observe task cancellation, and it does not make a queued or already-started Keychain operation cancellable.
Create one RAsyncKeychain per service and reuse it. Each instance owns a private serial queue, so avoid creating new instances from frequently evaluated code such as a SwiftUI view body.
enum AppSecrets {
static let keychain = RAsyncKeychain(service: "com.example.app")
}struct Session: Codable, Equatable {
var token: String
var userID: String
}
let session = Session(token: "secret", userID: "user-123")
try keychain.set(session, forKey: "session")
let restored = try keychain.object(Session.self, forKey: "session")Keychain Services are synchronous APIs. Avoid frequent Keychain access on performance-sensitive main-thread paths. Prefer loading small values once, keeping them in memory while needed, and writing back when state changes.
Use Keychain as secure local storage for small secrets. For purchase state, account state, or server-backed authorization, keep the authoritative source in StoreKit, your receipt validation flow, or your server-side system, and treat Keychain as a durable local cache.
For examples and migration notes, see Usage, KeychainAccess Migration, and Best Practices.
Ritsurin is available under the MIT license. See LICENSE.
