Skip to content

Repository files navigation

English | 日本語

Ritsurin

CI Release Swift 6.1+ iOS 16+ | macOS 13+ MIT License

Ritsurin

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.

Features

  • Swift 6 language mode
  • Typed throws with RKeychainError
  • Sync and async/await APIs
  • String, Data, and JSON Codable convenience APIs
  • KeychainAccess-style string subscript for migration-friendly call sites
  • Generic password items only
  • Zero third-party dependencies
  • iOS 16+ and macOS 13+

Non-Goals

Ritsurin does not implement custom cryptography, replace the system keychain, or provide biometric, Secure Enclave, shared web credentials, or internet password APIs.

When to Use Ritsurin

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 service and key coordinates;
  • typed errors instead of broad OSStatus handling at every call site;
  • String, Data, or JSON Codable values 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.

Installation

Ritsurin is designed for Swift Package Manager.

.package(url: "https://github.com/KoichiroKAMADA/Ritsurin.git", .upToNextMinor(from: "0.1.0"))

Docs and Guides

  • 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.

Ask Your Coding Agent If Ritsurin Fits

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.

Usage

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"] = nil

The subscript silently discards errors. Prefer the throwing APIs in new code.

Keychain Coordinates

Ritsurin works with generic password items. The main coordinates are:

  • service: the kSecAttrService value shared by related items;
  • key: the kSecAttrAccount value 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.

Async/Await

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")
}

Codable

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")

Best Practices

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.

License

Ritsurin is available under the MIT license. See LICENSE.

About

A thin, type-safe Swift 6 wrapper for generic password items in Apple Keychain Services.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages