axiom-keychain-ref について
iOS と macOS の Keychain Services を使う開発者向け API リファレンスです。SecItem 操作、項目属性、一意性ルール、アクセシビリティ、戻り値をまとめ、正確なクエリや保存処理を支援します。
主な機能
- 01SecItem CRUD 関数の動作
- 02Keychain 項目クラスと属性
- 03項目クラス別の一意性ルール
- 04検索・戻り値プロパティの一覧
- 05アクセシビリティとアクセス制御
活用シーン
- 01Keychain の追加・検索・更新・削除クエリ作成
- 02一意性ルールによる重複項目エラーの診断
- 03戻り値の型とアクセシビリティの選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-keychain-refインストール後、Claude Code で/axiom-keychain-refを入力して読み込まれたか確認します。
詳細
Keychain Services API リファレンス
iOS/macOS Keychain Services の包括的な API リファレンスです。SecItem の CRUD 関数、アイテムクラスの属性、一意性の制約、アクセシビリティレベル、アクセス制御フラグ、生体認証との統合、エラーコードを網羅しています。
クイックリファレンス
// Add a generic password
let addQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "[email protected]",
kSecValueData as String: "secret".data(using: .utf8)!
]
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
// Read a generic password
let readQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "[email protected]",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let readStatus = SecItemCopyMatching(readQuery as CFDictionary, &result)
let data = result as? Data
// Update a generic password
let updateQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "[email protected]"
]
let updateAttributes: [String: Any] = [
kSecValueData as String: "newSecret".data(using: .utf8)!
]
let updateStatus = SecItemUpdate(updateQuery as CFDictionary, updateAttributes as CFDictionary)
// Delete a generic password
let deleteQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "[email protected]"
]
let deleteStatus = SecItemDelete(deleteQuery as CFDictionary)
SecItem 関数
SecItemAdd
func SecItemAdd(_ attributes: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus
attributes 辞書で受け付けるもの:アイテムクラス + アイテム属性 + 値プロパティ + 戻り値の型プロパティ。
検索プロパティ(kSecMatch*)は受け付けません。追加クエリで kSecMatchLimit を指定するとエラーになります。
result:追加したアイテムを受け取る必要がない場合は nil を渡します。kSecReturn* キーで指定した形式のアイテムを受け取るには、ポインターを渡します。ほとんどの場合は nil を渡してください。結果の取得を要求すると、追加の読み取りが発生します。
SecItemCopyMatching
func SecItemCopyMatching(_ query: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus
query 辞書で受け付けるもの:アイテムクラス + アイテム属性 + 検索プロパティ + 戻り値の型プロパティ。
値プロパティ(kSecValueData)は検索条件として受け付けません。
result:型は、設定されている kSecReturn* キーによって異なります。
kSecReturnDataのみ →CFDatakSecReturnAttributesのみ →CFDictionarykSecReturnRefのみ →SecKey/SecCertificate/SecIdentitykSecReturnPersistentRefのみ →CFData(永続参照)- 複数の
kSecReturn*キー → 要求された型を含むCFDictionary kSecMatchLimit = kSecMatchLimitAll→ 上記の型を含むCFArray
SecItemUpdate
func SecItemUpdate(_ query: CFDictionary, _ attributesToUpdate: CFDictionary) -> OSStatus
query 辞書で受け付けるもの:アイテムクラス + アイテム属性 + 検索プロパティ。更新するアイテムの検索に使用します。
attributesToUpdate 辞書で受け付けるもの:アイテム属性 + 値プロパティ。これらは一致したアイテムに適用されます。アイテムクラスや検索プロパティは受け付けません。
kSecValueData を更新すると、保存されているシークレットが置き換えられます。属性(例:kSecAttrLabel)を更新すると、シークレットには触れずにメタデータが変更されます。
SecItemDelete
func SecItemDelete(_ query: CFDictionary) -> OSStatus
query 辞書で受け付けるもの:アイテムクラス + アイテム属性 + 検索プロパティ。
macOS では、デフォルトで一致するアイテムをすべて削除します(暗黙的な kSecMatchLimitAll)。iOS でも、一致するアイテムをすべて削除します。確認は行われず、削除は即座に実行されます。
アイテムクラス
kSecClassGenericPassword
汎用のシークレットストレージです。最もよく使用されるクラスです。
| 属性 | キー | 型 |
|---|---|---|
| サービス | kSecAttrService | CFString |
| アカウント | kSecAttrAccount | CFString |
| アクセスグループ | kSecAttrAccessGroup | CFString |
| アクセス可能性 | kSecAttrAccessible | CFString(定数) |
| 同期可能 | kSecAttrSynchronizable | CFBoolean |
| ラベル | kSecAttrLabel | CFString |
| 説明 | kSecAttrDescription | CFString |
| コメント | kSecAttrComment | CFString |
| 汎用 | kSecAttrGeneric | CFData |
| 作成者 | kSecAttrCreator | CFNumber(FourCharCode) |
| タイプ | kSecAttrType | CFNumber(FourCharCode) |
| 作成日 | kSecAttrCreationDate | CFDate(読み取り専用) |
| 変更日 | kSecAttrModificationDate | CFDate(読み取り専用) |
kSecClassInternetPassword
URL に関連付けられた認証情報です。必要になることはまれで、ほとんどのアプリでは汎用パスワードを使用します。
| 属性 | キー | 型 |
|---|---|---|
| サーバー | kSecAttrServer | CFString |
| プロトコル | kSecAttrProtocol | CFString(定数) |
| ポート | kSecAttrPort | CFNumber |
| パス | kSecAttrPath | CFString |
| アカウント | kSecAttrAccount | CFString |
| 認証タイプ | kSecAttrAuthenticationType | CFString(定数) |
| セキュリティドメイン | kSecAttrSecurityDomain | CFString |
| アクセス可能性 | kSecAttrAccessible | CFString(定数) |
| アクセスグループ | kSecAttrAccessGroup | CFString |
| 同期可能 | kSecAttrSynchronizable | CFBoolean |
| ラベル | kSecAttrLabel | CFString |
| コメント | kSecAttrComment | CFString |
| 作成者 | kSecAttrCreator | CFNumber(FourCharCode) |
| タイプ | kSecAttrType | CFNumber(FourCharCode) |
kSecClassCertificate
X.509 証明書です。通常はシステムが管理し、アプリのコードでは管理しません。
| 属性 | キー | 型 |
|---|---|---|
| サブジェクト | kSecAttrSubject | CFData(読み取り専用) |
| 発行者 | kSecAttrIssuer | CFData(読み取り専用) |
| シリアル番号 | kSecAttrSerialNumber | CFData(読み取り専用) |
| サブジェクトキー ID | kSecAttrSubjectKeyID | CFData(読み取り専用) |
| 公開鍵ハッシュ | kSecAttrPublicKeyHash | CFData(読み取り専用) |
| 証明書の種類 | kSecAttrCertificateType | CFNumber |
| 証明書のエンコーディング | kSecAttrCertificateEncoding | CFNumber |
| ラベル | kSecAttrLabel | CFString |
| アクセスグループ | kSecAttrAccessGroup | CFString |
| 同期可能 | kSecAttrSynchronizable | CFBoolean |
kSecClassKey
暗号鍵(RSA、EC、AES)です。暗号化、署名、鍵共有に使用されます。
| 属性 | キー | 型 |
|---|---|---|
| 鍵クラス | kSecAttrKeyClass | CFString(定数) |
| アプリケーションラベル | kSecAttrApplicationLabel | CFData |
| アプリケーションタグ | kSecAttrApplicationTag | CFData |
| 鍵の種類 | kSecAttrKeyType | CFString(定数) |
| ビット単位の鍵サイズ | kSecAttrKeySizeInBits | CFNumber |
| 実効鍵サイズ | kSecAttrEffectiveKeySize | CFNumber |
| 永続 | kSecAttrIsPermanent | CFBoolean |
| 機密 | kSecAttrIsSensitive | CFBoolean |
| 抽出可能 | kSecAttrIsExtractable | CFBoolean |
| ラベル | kSecAttrLabel | CFString |
| アクセスグループ | kSecAttrAccessGroup | CFString |
| 同期可能 | kSecAttrSynchronizable | CFBoolean |
| トークン ID | kSecAttrTokenID | CFString |
kSecClassIdentity
デジタル ID は、秘密鍵と組み合わせられた証明書です。独立したストレージクラスではなく、一致する証明書と鍵からシステムが合成します。kSecClassIdentity 項目を直接追加することはできません。証明書と鍵を個別に追加してください。両方が同じ kSecAttrPublicKeyHash を共有している場合、クエリは ID を返します。
Quinn「The Eskimo!」の技術ノート「SecItem: Pitfalls and Best Practices」(forums/thread/724013)を参照してください。デジタル ID は保存された項目ではなく、仮想的な結合です。
クラスごとの一意性制約
各キーチェーン項目は、属性の一部によって一意に識別されます。同じ主キーを持つ項目を 2 つ目として追加すると、errSecDuplicateItem(-25299)が返されます。既存の項目を変更するには、SecItemUpdate を使用してください。
| クラス | 主キー属性 |
|---|---|
| 汎用パスワード | kSecAttrService + kSecAttrAccount + kSecAttrAccessGroup + kSecAttrSynchronizable |
| インターネットパスワード | kSecAttrServer + kSecAttrPort + kSecAttrProtocol + kSecAttrAuthenticationType + kSecAttrPath + kSecAttrAccount + kSecAttrAccessGroup + kSecAttrSynchronizable |
| 証明書 | kSecAttrCertificateType + kSecAttrIssuer + kSecAttrSerialNumber + kSecAttrAccessGroup + kSecAttrSynchronizable |
| 鍵 | kSecAttrApplicationLabel + kSecAttrApplicationTag + kSecAttrKeyType + kSecAttrKeySizeInBits + kSecAttrEffectiveKeySize + kSecAttrKeyClass + kSecAttrAccessGroup + kSecAttrSynchronizable |
| ID | N/A(証明書 + 鍵の仮想的な結合) |
結果:一意な kSecAttrAccount 値を設定せずに、同じ kSecAttrService の下に複数ユーザーのトークンを保存すると、2 人目のユーザーに対する SecItemAdd は errSecDuplicateItem を返します。
属性定数リファレンス
ID 属性
| 定数 | 型 | 使用対象 |
|---|---|---|
kSecAttrService | CFString | GenericPassword |
kSecAttrAccount | CFString | GenericPassword、InternetPassword |
kSecAttrServer | CFString | InternetPassword |
kSecAttrLabel | CFString | すべてのクラス |
kSecAttrDescription | CFString | GenericPassword、InternetPassword |
kSecAttrComment | CFString | GenericPassword、InternetPassword |
kSecAttrGeneric | CFData | GenericPassword |
セキュリティ属性
| 定数 | 型 | 使用対象 |
|---|---|---|
kSecAttrAccessible | CFString(定数) | すべてのクラス |
kSecAttrAccessControl | SecAccessControl | すべてのクラス |
kSecAttrAccessGroup | CFString | すべてのクラス |
kSecAttrSynchronizable | CFBoolean | すべてのクラス |
kSecAttrAccessible と kSecAttrAccessControl は相互に排他的です。両方を設定するとエラーになります。kSecAttrAccessControl の作成時にアクセシビリティレベルが含まれるためです。
トークン属性
| 定数 | 型 | 用途 |
|---|---|---|
kSecAttrTokenID | CFString | キーをハードウェアトークンに紐付けます |
kSecAttrTokenIDSecureEnclave | CFString(値) | Secure Enclave — EC キーのみ(256 ビット) |
キーメタデータ属性
| 定数 | 型 | 値 |
|---|---|---|
kSecAttrKeyType | CFString | kSecAttrKeyTypeRSA、kSecAttrKeyTypeECSECPrimeRandom |
kSecAttrKeySizeInBits | CFNumber | 256(EC)、2048/4096(RSA) |
kSecAttrKeyClass | CFString | kSecAttrKeyClassPublic、kSecAttrKeyClassPrivate、kSecAttrKeyClassSymmetric |
kSecAttrApplicationTag | CFData | キーの検索に使用するアプリ定義のタグ |
kSecAttrApplicationLabel | CFData | 公開鍵の SHA-1 ハッシュ(自動生成) |
検索プロパティ
SecItemCopyMatching、SecItemUpdate(クエリパラメーター)、および SecItemDelete のクエリで使用します。
| 定数 | 型 | 用途 |
|---|---|---|
kSecMatchLimit | CFString または CFNumber | 最大結果数 — kSecMatchLimitOne、kSecMatchLimitAll、または明示的な整数上限を指定する CFNumber(例:結果を 5 件に制限) |
kSecMatchCaseInsensitive | CFBoolean | 文字列属性の大文字と小文字を区別しない照合 |
kSecMatchLimit のデフォルト
デフォルト値は状況によって異なり、バグのよくある原因です。
| 関数 | デフォルト | 動作 |
|---|---|---|
SecItemCopyMatching | kSecMatchLimitOne | 最初に一致した項目を返します |
SecItemDelete | すべて一致 | 一致する項目をすべて削除します |
意図を明確にするため、SecItemCopyMatching では常に kSecMatchLimit を明示的に設定してください。SecItemDelete で kSecMatchLimit を省略すると、一致する項目がすべて削除されます。これは仕様であり、バグではありません。
戻り値の型プロパティ
SecItemCopyMatching と SecItemAdd の戻り値を制御します。クエリ辞書に設定します。
| 定数 | 戻り値 | 結果の型 |
|---|---|---|
kSecReturnData | シークレット(パスワードのバイト列、キーデータ) | CFData |
kSecReturnAttributes | 項目メタデータ辞書 | CFDictionary |
kSecReturnRef | キーチェーンオブジェクトの参照 | SecKey、SecCertificate、または SecIdentity |
kSecReturnPersistentRef | 永続参照(アプリの再起動後も保持されます) | CFData |
クラスごとの戻り値の型の動作
| クラス | kSecReturnData | kSecReturnRef |
|---|---|---|
| 汎用パスワード | パスワードのバイト列 | 該当なし(参照型なし) |
| インターネットパスワード | パスワードのバイト列 | 該当なし(参照型なし) |
| 証明書 | DER エンコードされた証明書データ | SecCertificate |
| キー | キーデータ(抽出可能な場合) | SecKey |
| ID | 該当なし | SecIdentity |
複数の戻り値の型
複数の kSecReturn* キーが true の場合、結果は次のキーを持つ CFDictionary になります。
kSecValueData→ データkSecValueRef→ 参照kSecValuePersistentRef→ 永続参照kSecReturnAttributesがtrueの場合は、すべての属性キーも含まれます
kSecMatchLimitAll が設定されている場合、結果は上記の要素を含む CFArray になります。
値の型プロパティ
追加、クエリ、および更新の各辞書で、値の指定または取得に使用します。
| 定数 | 型 | 用途 |
|---|---|---|
kSecValueData | CFData | シークレット(パスワード、キー素材) |
kSecValueRef | SecKey / SecCertificate / SecIdentity | キーチェーンオブジェクトの参照 |
kSecValuePersistentRef | CFData | 項目への永続参照 |
操作ごとの動作
| プロパティ | SecItemAdd | SecItemCopyMatching | SecItemUpdate |
|---|---|---|---|
kSecValueData | シークレットを設定します | 検索条件としては無効です | シークレットを置き換えます |
kSecValueRef | 参照先のオブジェクトを追加します | 参照で検索します | 無効です |
kSecValuePersistentRef | 無効です | 永続参照で検索します | 無効です |
アクセシビリティ定数
キーチェーン項目を読み取り可能にするタイミングを制御します。kSecAttrAccessible で設定します。
| 定数 | 利用可能なタイミング | バックアップに保持 | iCloud 経由で同期 |
|---|---|---|---|
kSecAttrAccessibleWhenUnlocked | デバイスのロック解除中 | はい | はい(デフォルト) |
kSecAttrAccessibleAfterFirstUnlock | 初回のロック解除後から再起動まで | はい | はい |
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly | デバイスのロック解除中、かつパスコード設定済み | いいえ | いいえ |
kSecAttrAccessibleWhenUnlockedThisDeviceOnly | デバイスのロック解除中 | いいえ | いいえ |
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly | 初回のロック解除後から再起動まで | いいえ | いいえ |
デフォルト:新しい項目には kSecAttrAccessibleWhenUnlocked が適用されます。
ThisDeviceOnly バリアント:項目は暗号化されたバックアップに含まれず、iCloud Keychain 経由で同期されません。デバイスに紐付いたシークレット(生体認証で保護されたトークン、Secure Enclave キーなど)に使用します。
WhenPasscodeSetThisDeviceOnly:ユーザーがパスコードを削除すると、項目も削除されます。パスコードの削除後も保持してはならないシークレットに使用します。
AfterFirstUnlock:再起動後にユーザーが一度ロックを解除すると、バックグラウンドで利用可能になります。バックグラウンドフェッチ、プッシュ通知ハンドラー、バックグラウンドの URLSession 完了処理に必要です。
非推奨(使用しないでください):kSecAttrAccessibleAlways、kSecAttrAccessibleAlwaysThisDeviceOnly。
SecAccessControlCreateFlags
キーチェーン項目に対するきめ細かなアクセス制御です。SecAccessControlCreateWithFlags で作成し、kSecAttrAccessControl で設定します。
すべてのフラグ
| フラグ | 用途 |
|---|---|
.userPresence | 生体認証またはデバイスのパスコード |
.biometryAny | 登録済みの生体認証(新たな登録後も有効) |
.biometryCurrentSet | 現在の生体認証セットのみ(生体認証情報の変更時に無効化) |
.devicePasscode | デバイスのパスコードが必要です |
.privateKeyUsage | Secure Enclave キーの署名操作に必要です |
.applicationPassword | アプリが提供するパスワード(ほかの認証要素に加えて使用) |
.watch | ペアリング済みの Apple Watch で認証できます |
.or | 論理 OR でフラグを組み合わせます(いずれか 1 つを満たせば認証できます) |
.and | 論理 AND でフラグを組み合わせます(すべてを満たす必要があります) |
アクセス制御の作成
var error: Unmanaged<CFError>?
guard let accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.biometryCurrentSet, .or, .devicePasscode],
&error
) else {
let nsError = error!.takeRetainedValue() as Error
fatalError("Failed to create access control: \(nsError)")
}
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "auth-token",
kSecAttrAccessControl as String: accessControl,
kSecValueData as String: tokenData
]
let status = SecItemAdd(query as CFDictionary, nil)
フラグの組み合わせ
| 組み合わせ | 意味 |
|---|---|
[.biometryAny] | 登録済みの指紋または顔認証 |
[.biometryCurrentSet] | 現在の指紋または顔認証セット(再登録すると無効化) |
[.biometryCurrentSet, .or, .devicePasscode] | 生体認証またはパスコードによるフォールバック |
[.biometryCurrentSet, .and, .applicationPassword] | 生体認証とアプリのパスワード |
[.privateKeyUsage] | Secure Enclave キー操作(署名、復号) |
[.biometryAny, .or, .watch] | 生体認証またはペアリング済みの Watch |
.biometryAny と .biometryCurrentSet の違い:銀行トークンなど、セキュリティ要件の高い項目には .biometryCurrentSet を使用します。ユーザーが新しい指紋を登録すると、項目にアクセスできなくなります。その場合、アプリで再認証を行い、項目を再保存する必要があります。新たな登録後もアクセスを無効にする必要のない、利便性を重視する項目には .biometryAny を使用します。
LocalAuthentication との統合
キーチェーンでの LAContext
LAContext で生体認証を事前評価してから、そのコンテキストをキーチェーンクエリに渡すと、2 回目の生体認証プロンプトを避けられます。
import LocalAuthentication
let context = LAContext()
context.localizedReason = "Access your credentials"
var authError: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) else {
// Biometrics unavailable — handle error or fall back to passcode
return
}
context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Authenticate to access credentials"
) { success, error in
guard success else { return }
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "auth-token",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseAuthenticationContext as String: context
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
}
LAContext の Keychain キー
| キー | 型 | 目的 |
|---|---|---|
kSecUseAuthenticationContext | LAContext | 認証済みのコンテキストを再利用します(二重のプロンプトを回避します) |
kSecUseAuthenticationUI | CFString | UI の動作を制御します:kSecUseAuthenticationUIAllow(デフォルト)、kSecUseAuthenticationUIFail、kSecUseAuthenticationUISkip |
kSecUseAuthenticationUIFail:生体認証のプロンプトを表示せずに、errSecInteractionNotAllowed を返します。UI を表示せずにアイテムの存在を確認する場合に使用します。
LAPolicy の種類
| ポリシー | 要件 |
|---|---|
.deviceOwnerAuthenticationWithBiometrics | Face ID または Touch ID のみ |
.deviceOwnerAuthentication | 生体認証またはパスコードによるフォールバック |
BiometryType の検出
let context = LAContext()
var error: NSError?
context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
switch context.biometryType {
case .faceID: // Face ID available
case .touchID: // Touch ID available
case .opticID: // Optic ID available (visionOS)
case .none: // No biometric hardware
@unknown default: break
}
LAError コード
| エラー | コード | 原因 |
|---|---|---|
.authenticationFailed | -1 | ユーザーの認証に失敗しました |
.userCancel | -2 | ユーザーが「キャンセル」をタップしました |
.userFallback | -3 | ユーザーが「パスワードを入力」をタップしました |
.systemCancel | -4 | システムがキャンセルしました(アプリがバックグラウンドに移行) |
.passcodeNotSet | -5 | パスコードが設定されていません |
.biometryNotAvailable | -6 | ハードウェアが利用できないか、制限されています |
.biometryNotEnrolled | -7 | 生体認証が登録されていません |
.biometryLockout | -8 | 認証の失敗が多すぎます |
OSStatus エラーコード
よく使われる Keychain の OSStatus 値と、その根本原因です。
| エラー | コード | 説明 | よくある原因 |
|---|---|---|---|
errSecSuccess | 0 | 操作に成功しました | — |
errSecDuplicateItem | -25299 | アイテムはすでに存在します | 同じ主キーで追加しようとしています — 代わりに SecItemUpdate を使用してください |
errSecItemNotFound | -25300 | 一致するアイテムがありません | クエリ属性が間違っているか、アイテムが保存されていません |
errSecInteractionNotAllowed | -25308 | UI プロンプトがブロックされました | アイテムに認証が必要ですがデバイスがロックされているか、kSecUseAuthenticationUIFail が設定されています |
errSecAuthFailed | -25293 | 認証に失敗しました | パスワードが間違っている、生体認証に失敗した、または ACL によって拒否されました |
errSecMissingEntitlement | -34018 | Keychain のエンタイトルメントがありません | アプリに keychain-access-groups エンタイトルメントがありません — ユニットテストターゲットでよく発生します |
errSecNoSuchAttr | -25303 | 属性が見つかりません | アイテムクラスで有効ではない属性をクエリしています |
errSecParam | -50 | パラメーターが無効です | クエリ辞書の形式が不正です — 型の不一致(例:Data が必要な箇所に String を指定)を確認してください |
errSecAllocate | -108 | メモリを確保できませんでした | システムリソースが枯渇しています |
errSecDecode | -26275 | データをデコードできません | アイテムが破損しているか、エンコード形式が一致しません |
errSecNotAvailable | -25291 | Keychain を利用できません | Keychain データベースがありません(まれなケース — Simulator のリセット、またはインストールの破損) |
Swift での OSStatus の解釈
let status = SecItemAdd(query as CFDictionary, nil)
if status != errSecSuccess {
let message = SecCopyErrorMessageString(status, nil) as? String ?? "Unknown error"
print("Keychain error \(status): \(message)")
}
テストターゲットでの -34018
ユニットテストランナー(XCTest)には、keychain-access-groups エンタイトルメントがないことがよくあります。回避策:
- テストターゲットにホストアプリケーションを追加します(Xcode → テストターゲット → General → Host Application)
- ユニットテストではメモリ内モックを使用し、実際の Keychain は統合テストでのみ使用します
Keychain の共有
アクセスグループ
デフォルトでは、アイテムはアプリごとに分離されています。アプリ間または拡張機能間で共有するには:
- Xcode で「Keychain Sharing」ケイパビリティを有効にします
- 共有アクセスグループの識別子を追加します
- アイテムを追加する際に
kSecAttrAccessGroupを設定します
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.shared",
kSecAttrAccount as String: "shared-token",
kSecAttrAccessGroup as String: "TEAMID.com.example.shared",
kSecValueData as String: tokenData
]
アクセスグループの形式は $(TeamIdentifierPrefix)$(GroupIdentifier) です。明示的なアクセスグループが設定されていないアイテムは、エンタイトルメント内のアプリの最初のアクセスグループがデフォルトになります。
iCloud Keychain の同期
kSecAttrSynchronizable を true に設定すると、iCloud Keychain 経由で同期されます:
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app",
kSecAttrAccount as String: "sync-token",
kSecAttrSynchronizable as String: true,
kSecValueData as String: tokenData
]
同期可能なアイテムでは、ThisDeviceOnly のアクセシビリティレベルや SecAccessControl は使用できません。kSecAttrAccessibleWhenUnlocked または kSecAttrAccessibleAfterFirstUnlock を使用する必要があります。
クエリ時、kSecAttrSynchronizable のデフォルトは kSecAttrSynchronizableAny です(ローカルアイテムと同期済みアイテムの両方を返します)。絞り込むには、true または false を明示的に設定します。
Resources
WWDC: 2013-709, 2014-711, 2020-10147
Docs: /security/keychain_services, /localauthentication, /security/secaccesscontrolcreateflags, /security/secitemadd(::)
Skills: axiom-code-signing-ref, axiom-app-attest