Keychainは、パスワード、暗号鍵、証明書、機密メモを安全に保存するために設計されたiOSのセキュアストレージです。Apple Security Documentation (2025)によると、KeychainはA7チップ以降のすべてのデバイスでSecure Enclaveを介したハードウェア暗号化を使用しています。iOS Keychainのアーキテクチャを理解することは、すべての開発者がアプリケーションのトークンとシークレットを適切に保存するために不可欠です。
重要なポイント
iOS Keychainは、Appleのオペレーティングシステムに組み込まれた機密データを保存するためのセキュアなメカニズムです。UserDefaultsや通常のファイルとは異なり、Keychainはすべてのアイテムをハードウェアレベルで暗号化し、セキュリティポリシーに基づいたきめ細かいアクセス制御を提供します。
KeychainはiOS 2.0で導入され、それ以来大きな変化を遂げてきました:iOS 7ではSecure Enclaveを介したハードウェアキーのサポートが追加され、iOS 9ではAccess Groupsを介したアプリ間のKeychain共有が導入され、iOS 13ではLAContextを介した生体認証バインディングのサポートが追加されました。Apple WWDC Session(2024)によると、App Storeのトップ100にあるiOSアプリの90%以上が認証トークンの保存にKeychainを使用しています。
アーキテクチャ的には、Keychainはアプリケーションのサンドボックス外に配置された暗号化SQLiteデータベースです。各アイテム(SecItem)は個別のキーで暗号化され、そのキーはさらにSecure Enclaveのハードウェアキーによって保護されています。システムサービスのSecuritydは、アプリケーションのエンタイトルメントと要求された保護クラスに基づいてKeychainへのアクセスを管理します。
他のストレージ方法に対するKeychainの重要な利点:データはOSによって自動的に暗号化および復号化されます。開発者は手動で暗号化を実装する必要はありません - 正しいパラメータでSecItemAddを呼び出すだけです。iOSは、Keychainのデータを他のアプリケーションが読み取れないことを保証します(Access Groupsが適切に設定されている場合)。
Keychainのアーキテクチャには、物理(Secure Enclave)、システム(Security.framework)、アプリケーション(SecItem* API)、論理(Access Groups、Protection Classes)の複数のレベルが含まれます。各レベルを理解することで、シークレットストレージを適切に設計できます。
Keychainを操作するための主要APIはSecurity frameworkの関数です:追加用のSecItemAdd、読み取り用のSecItemCopyMatching、更新用のSecItemUpdate、削除用のSecItemDelete。各関数は、検索または保存するアイテムの属性を記述したクエリディクショナリを受け取ります。
主要なクエリ属性:kSecClass - アイテムタイプ(kSecClassGenericPassword、kSecClassKey、kSecClassCertificate)、kSecAttrAccount - クラス内の一意の識別子、kSecValueData - 保存するデータ(Data)、kSecAttrAccessible - 保護クラス。SecItemCopyMatchingはkSecReturnDataフラグとともにアイテムデータを返し、kSecMatchLimitとともに結果の数を返します。
重要:すべての関数はOSStatusを返します。成功した操作はerrSecSuccess(0)を返します。エラー:errSecItemNotFound(-25300)- アイテムが見つかりません、errSecDuplicateItem(-25299)- アイテムは既に存在します、errSecAuthFailed(-25293)- 生体認証に失敗しました。開発者は各ステータスを適切に処理する必要があります。
Protection Classは、Keychain内のデータがいつ読み取り可能かを決定するkSecAttrAccessible属性です。iOSは、可用性とセキュリティの異なるレベルを持つ6つの保護クラスをサポートしています。
ほとんどのシナリオで推奨されるクラスはkSecAttrAccessibleWhenUnlockedThisDeviceOnlyです:データはデバイスのロックが解除されている場合のみ利用可能で、iCloud Backupにコピーされません。再起動後に利用可能にする必要があるデータ(ただし最初のロック解除後のみ)には、kSecAttrAccessibleAfterFirstUnlockThisDeviceOnlyを使用します。毎回のアクセスに生体認証が必要な重要なデータには、kSecAttrAccessibleWhenUnlockedThisDeviceOnlyを生体認証を要求するACLと組み合わせます。
ThisDeviceOnlyサフィックスのないクラス(kSecAttrAccessibleWhenUnlocked、kSecAttrAccessibleAfterFirstUnlock)は、iCloud Backupへのコピーを許可します。これはユーザーにとって便利ですが、セキュリティを低下させます - データはバックアップから復元される可能性があります。認証トークンには常にThisDeviceOnlyを使用してください。
Access Control List(ACL)は、ユーザー認証に基づいてKeychainアイテムの操作を制限するメカニズムです。ACLはSecAccessControlCreateWithFlagsを介して設定され、アイテム保存時にkSecAttrAccessControl属性に渡されます。
サポートされているフラグ:kSecAccessControlUserPresence - 任意の認証(Face ID、Touch ID、またはパスコード)、kSecAccessControlBiometryCurrentSet - 生体認証のみ(現在登録されている指紋または顔)、kSecAccessControlDevicePasscode - パスコードのみ。ACLはすべての操作に適用されます:アイテムの読み取り、更新、削除にも認証が必要です。
iOS 15+では、Apple Watch用のフラグkSecAccessControlWatchが登場しました。ペアリングされたウォッチを介した認証を許可します。ACLは組み合わせることができます:例えば、kSecAccessControlUserPresenceまたはkSecAccessControlBiometryAnyとオプションのパスコード(.orオリエンテーション)。
iOS Keychainは4つの主要なアイテムクラス(kSecClass)をサポートしており、それぞれが独自のデータタイプ向けに設計されています。適切なクラスを選択することで、整理とアイテム検索が簡素化されます。
kSecClassGenericPassword - 汎用パスワード:最も一般的に使用されるクラス。一意のキー(kSecAttrAccount)を持つ任意のバイナリデータ(Data)を保存します。トークン、APIキー、PINコードに適しています。使用に追加のエンタイトルメントは必要ありません。
kSecClassInternetPassword - インターネットパスワード:ネットワークリソースに関連付けられたデータを保存します。追加属性:kSecAttrServer(サーバードメイン)、kSecAttrProtocol(https、ftp)、kSecAttrPort、kSecAttrAuthenticationType。iOSはAutoFillを介してこれらのパスワードを自動入力できます。
kSecClassKey - 暗号鍵:暗号化キー(AES、RSA、EC)を保存するため。キーはDataではなくSecKeyRefとして保存されます。kSecClassCertificate - デジタル証明書を保存および検証するためのX.509証明書。両方のクラスは、暗号操作の理解と適切な属性設定が必要です。
実際には、モバイルアプリでのKeychain使用の95%は、認証トークン保存用のkSecClassGenericPasswordと秘密暗号鍵保存用のkSecClassKeyでカバーされます。kSecClassCertificateはめったに使用されません - 通常は独自のPKIを持つエンタープライズアプリで使用されます。
Security frameworkを使用してSwiftでKeychainを操作する実践的な例を見てみましょう。各例にはエラーハンドリングと適切なProtection Classの設定が含まれています。
基本的な例では、WhenUnlockedThisDeviceOnly保護を使用してKeychainに認証トークンを保存します。キー(kSecAttrAccount)はサービス識別子、データ(kSecValueData)はData形式のトークンです。
import Security
enum KeychainError: Error {
case unexpectedStatus(OSStatus)
}
func saveToken(token: String, service: String) throws {
let data = Data(token.utf8)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: "auth_token",
kSecValueData as String: data,
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.unexpectedStatus(status)
}
}
func readToken(service: String) throws -> String {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: "auth_token",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(
query as CFDictionary, &result
)
guard status == errSecSuccess,
let data = result as? Data else {
throw KeychainError.unexpectedStatus(status)
}
return String(decoding: data, as: UTF8.self)
}
この例では、SecAccessControlCreateWithFlagsを使用してキーを生体認証にバインドする方法を示します。アイテムへの各アクセスにはFace IDまたはTouch IDが必要になります。
import LocalAuthentication
func saveWithBiometry(data: Data, key: String) throws {
let accessControl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.biometryCurrentSet,
nil
)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessControl as String: accessControl as Any
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.unexpectedStatus(status)
}
}
この例では、同じ開発者のアプリ間で共有Keychainアクセス用のAccess Groupを設定する方法を示します。keychain-access-groupsエンタイトルメントが必要です。
// Capabilities: Keychain Sharing 有効
// App IDs: group.com.example.shared
func saveSharedToken(token: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "shared_token",
kSecValueData as String: token,
kSecAttrAccessGroup as String:
"group.com.example.shared",
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemAdd(query as CFDictionary, nil)
}
iOS Keychainを適切に使用するには、一般的な脆弱性やデータ損失を防ぐいくつかの重要なルールに従う必要があります。
すべての認証シークレットにThisDeviceOnlyを使用する:kSecAttrAccessibleWhenUnlockedThisDeviceOnlyは、トークンがiCloud Backupに含まれないことを保証します。攻撃者がバックアップにアクセスしても、このフラグを持つKeychainデータは利用できません。例外は、ユーザーのすべてのデバイスで利用可能にする必要があるデータ(プロプライエタリサービスの暗号化キーなど)で、その場合はkSecAttrAccessibleWhenUnlockedをkSecAttrSynchronizableとともに使用します。
生のパスワードを保存しない - ハッシュまたはセッショントークンを保存します。Apple Security Guide(2025)は、Keychainにユーザーパスワードを平文で保存しないことを推奨しています。代わりに、OAuth 2.0を介した認証成功後にサーバーから受信したリフレッシュトークンを保存します。パスワードはトークンを取得するためだけに使用され、直ちにメモリから削除されます。
Keychainエラーを正しく処理する:各Keychain操作はOSStatusを返すため、確認する必要があります。特にerrSecItemNotFound(トークンの期限切れまたは削除)とerrSecAuthFailed(生体認証失敗)に注意してください。最初のケースでは、アプリは新しい認証を要求する必要があります。2番目のケースでは、ユーザーに代替方法(パスコード)を表示します。決してerrSecItemNotFoundステータスを無視しないでください - nilの読み取りを試みるとアプリがクラッシュします。
実際のデバイスでKeychainをテストする:シミュレーターにはSecure Enclaveがなく、生体認証ACLをサポートしていません。常にシナリオを確認してください:初回起動、バックアップからの復元、デバイスパスワード変更、アプリの削除と再インストール。実際のデバイスでは、アプリが削除されてもKeychainは保持されますが、kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlyフラグが使用されていない場合に限ります。このフラグはパスコードが削除されるとクリアされます。
Keychain操作の数を最小限に抑える:各読み取りまたは書き込み操作はシステムサービスSecuritydへの呼び出しであり、スレッドをブロックする可能性があります。セッション中は読み取ったトークンをメモリにキャッシュし、アプリの再起動時または認証エラー(サーバーからの401)時にのみKeychainに再度アクセスします。iOSはデバイスがロックされると自動的にKeychainをロックするため、生体認証リクエストとともにLAContextを介した読み取りを計画してください。
よくある質問
はい、保護クラスkSecAttrAccessibleAfterFirstUnlockThisDeviceOnlyまたはkSecAttrAccessibleAfterFirstUnlockを使用してください。データは再起動後の最初のデバイスロック解除後に利用可能になります。アプリ起動時に自動アクセスする場合(ロック解除を待たずに)は、kSecAttrAccessibleAlwaysを使用しますが、これはセキュリティを低下させます。
各データタイプのkSecClassを含むクエリでSecItemDeleteを呼び出します。すべてのアプリアイテムを完全にクリアするには、次を実行します:SecItemDelete([kSecClass as String: kSecClassGenericPassword] as CFDictionary)。kSecClassKey、kSecClassCertificate、kSecClassInternetPasswordについて繰り返します。
kSecAttrAccessibleはデータがいつ利用可能かを決定します(ロック解除時、最初のロック解除後など)。kSecAttrAccessControlは誰がアクセスできるかを決定します(生体認証、パスコード、任意の認証)。これらは組み合わされます:最初にProtection Class、次にACL。例えば、データはロック解除時にのみ、かつFace IDの後にのみ利用可能です。
理由:アイテムが保存されていない、パスコード削除時にアイテムが削除された(kSecAttrAccessibleWhenPasscodeSetを使用した場合)、アプリが再インストールされた(Keychainは保持されるが新しいデバイスでバックアップから復元されない)、Access Groupまたは開発者チーム識別子が変更された。kSecAttrServiceとkSecAttrAccountを確認してください。
LocalAuthenticationのLAContextを使用します:context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)を呼び出します。trueを返す場合 - デバイスはTouch IDまたはFace IDをサポートしています。Keychain ACLには、biometryCurrentSetフラグ(現在の生体データのみ)またはbiometryAny(以前に登録された任意のもの)を使用します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。