Keychain は、iOS、watchOS、macOS、tvOSにおける安全な認証情報ストレージで、パスワード、キー、証明書をハードウェアレベルで管理します。UserDefaultsとは異なり、Keychainのデータは暗号化され、アプリケーションのサンドボックスごとに隔離されます。Apple Developer Documentationによると、KeychainはSecure EnclaveメカニズムとAES-256ハードウェア暗号化を使用して機密情報を保護します。
重要ポイント
Keychain は、パスワード、暗号鍵、証明書、その他の機密データを安全に保存するために設計されたAppleオペレーティングシステムの安全なデータベースです。Keychainは1999年にMac OS 8.6で初めて登場し、iOSでは最初のSDKバージョンから利用可能です。
Keychainはモバイル開発の基本的な問題を解決します:安全でないファイルシステムでの秘密情報(トークン、パスワード、暗号鍵)の保存です。UserDefaultsやSQLiteとは異なり、Keychainのデータは暗号化された状態で保存され、ディスクに書き込まれる際に自動的に暗号化されます。
各iOSアプリケーションは独立したサンドボックスで動作し、自分のkeychainにのみアクセスできます。ただし、開発者はkeychain access groupsを通じて、同じ開発者のアプリケーション間でKeychainへの共有アクセスを設定できます。ユーザーのiCloud Keychainは、エンドツーエンド暗号化を通じてデバイス間でデータを同期します。
Apple Security White Paper(2025年)によると、Keychainはデバイスの一意識別子(UID)に紐づく鍵でAES-256ハードウェア暗号化を使用し、デバイスから抽出してもデータにアクセスできないようにします。
Keychainは階層型サービスとして機能します:アプリケーションがSecurity frameworkにアクセスし、Security frameworkが暗号化されたSQLite Keychainデータベースを管理するsecuritydデーモンと通信します。各レコードは個別の鍵で暗号化され、鍵自体はA7チップ以降のデバイスでSecure Enclaveハードウェアモジュールによって保護されます。
Security frameworkはKeychain Servicesを操作するためのCインターフェースを提供します。主な関数:SecItemAdd(追加)、SecItemCopyMatching(検索)、SecItemUpdate(更新)、SecItemDelete(削除)。各操作はリクエストタイプを記述するCFDictionaryRef属性辞書を受け取ります。
データはクラスによって分類されます:kSecClassGenericPassword(任意のパスワード)、kSecClassInternetPassword(インターネットサービスのパスワード)、kSecClassCertificate(証明書)、kSecClassKey(暗号鍵)、kSecClassIdentity(鍵と証明書のペア)。
パスワードを保存する際、アプリケーションは属性を含む辞書を作成します:サービス、アカウント、アクセスレベル、ラベル。Security framework はデータをsecuritydデーモンに渡し、デーモンがデバイスに紐づく鍵でレコードを暗号化してKeychain SQLiteに保存します。取得も同様に機能します:属性によるクエリ、検索、復号化、データの返却。
重要な特徴:アプリケーションが削除されると、iOSは自動的にそのアプリケーションが作成したKeychainレコードを消去します(iOS 10.3+以降)。アプリケーションを再インストールしても、iCloud Keychainを使用していない限り、以前のインストールのKeychainデータは利用できません。
import Security
func savePassword(service: String, account: String, password: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecValueData as String: password,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
SecItemAdd(query as CFDictionary, nil)
}
Keychainは5つの主要なデータクラスをサポートし、それぞれに独自の属性セットと適用範囲があります。クラスの選択により、利用可能な検索キーとデータの暗号化方法が決まります。
kSecClassGenericPassword クラスと kSecClassInternetPassword クラスは、文字列パスワードを保存するために設計されています。インターネット版はさらにサーバーデータ(ドメイン、プロトコル、ポート、認証パス)を保存します。GenericPasswordはサーバーに依存しない任意のパスワードの保存に使用されます。
インターネットパスワードを保存する際は、kSecAttrServer(ドメイン)、kSecAttrProtocol(HTTPS、FTPなど)、kSecAttrPort、kSecAttrPath を指定できます。これにより、AutoFillによる自動入力時にレコードを正確に識別できます。
kSecClassKey クラスは対称鍵(AES)と非対称鍵(RSA、EC)を保存します。kSecClassCertificate クラスはX.509証明書を保存します。kSecClassIdentity クラスは秘密鍵と対応する証明書を1つのレコードに結合します。
非対称鍵の場合、Keychainは属性をサポートします:kSecAttrKeyType(RSA、EC)、kSecAttrKeySizeInBits(2048、256)、kSecAttrIsPermanent(Keychainに保存するかどうか)。証明書の場合:kSecAttrLabel(人間が読める名前)、kSecAttrSubject(所有者のDN)、kSecAttrIssuer(発行者のDN)。
| Keychainクラス | 目的 | データ型 |
|---|---|---|
| kSecClassGenericPassword | アプリのパスワード | Data(NSString) |
| kSecClassInternetPassword | サーバーパスワード | Data + サーバー属性 |
| kSecClassCertificate | X.509証明書 | SecCertificate |
| kSecClassKey | 暗号鍵 | SecKey |
| kSecClassIdentity | 鍵 + 証明書 | SecIdentity |
ネイティブiOSコードでKeychainを操作するための主要なインターフェースは、Security frameworkの関数です。SwiftとObjective-Cでは、便利な関数にラップされた単一のCインターフェースが利用可能です。
SecItemAdd 関数は新しいアイテムをKeychainに追加します。クラス、データ、属性を含む辞書を入力として受け取り、ステータスerrSecSuccess(0)またはエラーコードを返します。SecItemCopyMatching 関数は属性による検索を実行し、見つかったデータを返します。
検索にはkSecReturnData フラグを使用します — trueの場合、関数はレコードの内容を返します。kSecMatchLimit フラグは結果の数を指定します:kSecMatchLimitOne(1つ)またはkSecMatchLimitAll(すべて)。
func readPassword(service: String, account: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else { return nil }
return item as? Data
}
SecItemUpdate は既存のレコードの属性やデータを変更できます。SecItemDelete は渡された辞書に一致するすべてのレコードを削除します。削除時には、共有keychain内の他のレコードを誤って削除しないよう、十分な属性を指定することが重要です。
func deletePassword(service: String, account: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account
]
SecItemDelete(query as CFDictionary)
}
各KeychainレコードにはkSecAttrAccessible 属性があり、データがいつ読み取り可能かを決定します。これはセキュリティ上非常に重要です:誤ったアクセスレベルはデータを脆弱にしたり、逆に必要なときに利用できなくしたりする可能性があります。
kSecAttrAccessibleWhenUnlocked — データはデバイスのロックが解除されている場合のみ利用可能。kSecAttrAccessibleAfterFirstUnlock — 再起動後の最初のロック解除後に利用可能。kSecAttrAccessibleAlways — 常に利用可能(推奨されません)。ThisDeviceOnly 接尾辞はバックアップによるデータ転送を防ぎます。
Secure Enclave 搭載デバイスでは、Keychainの暗号鍵を生体認証でさらに保護できます:kSecAccessControlBiometryCurrentSet または kSecAccessControlUserPresence。これにより、各読み取り操作にFace IDまたはTouch IDが必要になります。
iCloud Keychain はエンドツーエンド暗号化を通じてAppleデバイス間でレコードを同期します。Appleは復号化されたデータにアクセスできません。同期を有効にするには、kSecAttrSynchronizable 属性を値kCFBooleanTrueで追加します。
重要:同期されたレコードにはThisDeviceOnly 属性を含めることはできません。他のデバイスで利用可能である必要があるからです。すべてのデバイスを失った場合、iCloud KeychainはリカバリコードまたはiCloudリカバリキーを使用して復元できます。
認証トークンには、kSecAttrAccessibleWhenUnlockedThisDeviceOnlyを使用します — データはデバイスのロック解除時にのみ利用可能で、バックアップにコピーされません。バックグラウンドタスクで利用可能にする必要がある証明書には、kSecAttrAccessibleAfterFirstUnlockを使用します。機密データにkSecAttrAccessibleAlwaysを使用しないでください。
同期を必要としないデータには、常にThisDeviceOnly 接尾辞を追加することをお勧めします。これにより、iCloudやiTunesバックアップへの秘密情報の誤ったコピーを防ぎます。重要なデータについては、アクセシビリティとkSecAccessControlUserPresenceを組み合わせてください — これにより、レコードの復号化に生体認証またはパスコードが必要になります。
iCloud Keychain を使用する場合、同期されたレコードはすべてのユーザーデバイスで利用可能であることに注意してください:攻撃者が1つのデバイスにアクセスすると、すべてのデバイスのデータが危険にさらされる可能性があります。分離された秘密情報にはThisDeviceOnlyを使用してください。
よくある質問
UserDefaults はデータをプレーンテキストで.plistファイルに保存し、IPA解析中にアクセス可能です。Keychainは各レコードを個別に暗号化し、ハードウェア暗号化を使用し、アクセスレベル、生体認証、iCloud経由の同期をサポートします。
iOSシミュレータを起動するたびに、Keychainはクリアされます。実機では、アプリを削除すると、iOSがそのデベロッパーのすべてのKeychainレコードを削除します。選択的にクリアするには、適切な属性を指定してSecItemDeleteを呼び出します。
はい、keychain access groups を通じて可能です。アプリケーションはプロビジョニングプロファイルに同じteam IDを持ち、同じkeychain-access-groupsを持つentitlementsが必要です。デベロッパーはXcode Capabilitiesで共有アクセスを設定します。
Appleは厳格な制限を文書化していませんが、実際には1レコードあたり1〜4KBを超えないことが推奨されています。より大きな容量の場合は、ファイル暗号化を使用し、鍵をKeychainに保存し、データ自体はDocumentsに保存します。
ジェイルブレイク デバイスでは、攻撃者がルートアクセスを取得し、Keychain SQLiteを直接読み取ることができるため、Keychainの保護は低下します。重要なデータには、サーバーから取得した鍵による追加の暗号化を使用してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。