パスワードを暗号化して安全に iPhone/iPad へ保管したい。iOS はこの用途の為に Keychain Services を提供している。今回は Keychain Services について調べてみた。リファレンスの内容に加え、独自に調査・検証した結果をまとめてある。動作確認の為のサンプルも GitHub に置いておいた。
- 概要
- 利用方法
2.1 API
2.2 検索条件(query)
2.3 属性値(attributes)
2.4 クラス
2.5 属性の種類
2.6 ユニークキー
2.7 kSecAttrAccessible
2.8 エラーコード
- アクセス制御
3.1 Keychain Access Group(グループ)
3.2 アプリケーションの所属グループ
3.3 グループの変更
3.4 グループの命名ルール
3.5 デフォルトグループの決定ルール
3.6 アイテム登録時に指定可能なグループ
3.7 シミュレータにおけるグループ
3.8 グループ設定の例
- 開発・運用情報
4.1 API 利用方法
4.2 実機とシミュレータでの違い
4.3 アプリを削除した場合の挙動
- サンプル
5.1 登録・更新
5.2 削除
5.3 検索(パスワード取得)
5.4 検索(全アイテム取得)
- 運用上の注意点
- ソースコード
- 参考情報
- 付録
なお iOS で Keychain Services を使用する場合、いくつか重要な注意点がある。それらは「6. 運用上の注意点」に簡単にまとめたので参照されたい。
1. 概要
Keychain Services はパスワードや秘密鍵、証明書などを保存する安全なストレージとそれを操作するAPIを提供している。ストレージは暗号化されていてパスワード(keychain password)によって保護されている。このパスワードが無い限りストレージ内の情報を利用(復号化)することはできない。Keychain Services では格納しているパスワードや秘密鍵、証明書の情報をアイテムと呼んでいる。Keychain Services はこれら複数のアイテムを管理し、登録、変更、削除、検索するための API を提供している。
Mac OS X、iOS 共にこのサービスはサポートされているが、プラットフォームによって使い方が若干異なる。
プラット
フォーム | 他アプリの
情報へ
アクセス | 利用時パスワード要求
(keychain password) | パスワード指定方法
(keychain password) | 主に使用するAPI |
Mac OS X | ◯ | 求められる
※設定による | ユーザ指定 | SecKeychain系 |
iOS | △ | 求められない | システム自動生成 | SecItem系 |
他アプリケーションが格納した Keychain Services 内の情報へのアクセス
Mac OS X の場合はユーザが許可を与えれば他のアプリケーションの情報へアクセスすることができる。一方、iOS の場合、アプリケーションは自身が保存した情報のみアクセスが行える。他のアプリケーションの情報へは基本的にアクセスすることができない。ただし同じプロビジョニングプロファイルを使ってビルドされたアプリは設定により情報を共有することができる(後述)。
iOS での特記事項
- iOS には単一のキーチェーンのみ存在する(Mac OS X は複数)。
- iOS の場合、PC接続時にストレージの内容は暗号化されたままバックアップされる。これを復号化するパスワード(keychain password)はバックアップされない(iOSデバイスの中から外に持ち出されない)。
- Keychain Service はプロビジョニングファイルの情報を利用する。この為、アプリケーションをバージョンアップする場合、同じプロビジョニングファイルを使うことが推奨される。[*1]
[*1] アクセス権限を決定する Keychain Access Group の値にデフォルトでプロビジョニングファイルで定義される App Identifier が使用される為。
2. 利用方法
2.1 API
Security.framework で提供される下記の4つのAPIを使用して Keychain Services へアクセスする。
SecItemAdd (CFDictionaryRef attributes, CFTypeRef* result);
SecItemUpdate (CFDictionaryRef query, CFDictionary attributes);
SecItemDelete (CFDictionaryRef query);
SecItemCopyMatching(CFDictionaryRef query, CFTypeRef* result);
Keychain Services Reference - Functions
2.2 検索条件(query)
APIの引数 query は、Keychain Services 内のアイテムを検索したり、更新対象のアイテムを指定する時の条件として使用する。削除する場合の削除対象のアイテムもこの query で特定する。
例)CFDictionaryRef query
|--kSecClass = kSecClassGenericPassword // パスワードクラスを指定
|--kSecAttrAccount = @"hashiguchi" // アカウント
|--kSecRetrunAttributes = kCFBooleanTrue // 結果を CFDictionary型で受け取る
上記を SecItemCopyMatchingの第一引数へ渡すと、第二引数で結果を受け取ることができる。また SecItemUpdate での更新対象の指定、SecItemDelete での削除対象を指定するのにも使われる。
検索で使えるキーは次のようなものがある。
Search Keys
キー | 内容 |
kSecMatchPolicy | SecPolicyRefを条件とする
※証明書で使用 |
kSecMatchItemList | 指定した配列を検索対象にできる。また persistent reference から normalreference への変換や persistent reference を指定した削除などの用途でも利用。 |
kSecMatchSearchList | ※不明(リファレンスに説明なし)。kCFBooleanTrue を設定してみたが結果に変化なし。 |
kSecMatchIssuers | X.500 Issuer(証明書発行者)を配列で指定する
※証明書で使用 |
kSecMatchEmailAddressIfPresent | RFC822で定義されるemailを指定する
※証明書で使用 |
kSecMatchSubjectContains | X.500 Subject(証明書取得者)の部分一致文字列を指定する
※証明書で使用 |
kSecMatchCaseInsensitive | 文字列比較時の大文字小文字区別の有無 |
kSecMatchTrustedOnly | 信頼できる認証局に発行されている証明書かどうかを指定
※証明書で使用する |
kSecMatchValidOnDate | 証明書の有効期限を指定
※証明書で使用する |
kSecMatchLimit | 結果で一度に取得する最大件数を指定 |
kSecMatchLimitOne | 結果1件のみを取得する(デフォルト動作) |
kSecMatchLimitAll | 結果すべてを取得する(件数無制限) |
検索結果で受け取りたい値の種類・型をあらかじめ query に設定しておく。設定可能なキーは次の通り。
キー | 型 | 値 | 結果例(NSLog出力例) |
kSecReturnData | CFDataRef | パスワード | <62706c69 73743030 ... |
kSecReturnAttributes | CFDictionaryRef | 属性値 | {
acct = hashiguchi;
agrp = test;
gena = <70617373 776f7264>;
pdmn = ak;
svce = SampleService;
} |
kSecReturnRef | 下記のいずれか(クラスに依存)
SecKeychainItemRef
SecKeyRef
SecCertificateRef
SecIdentityRef
CFDataRef | ※クラスに依存 | ※kSecClassGenericPasswordでは戻り値なし |
kSecReturnPersistentRef | CFDataRef | ディスク上に格納されたオブジェクトへの参照、もしくは別プロセスとの共有オブジェクトへの参照を表す(※詳細未調査)。 | <67656e70 00000000 00000001> |
上記キーに値 CFBooleanTrue を指定すると有効になる。なお複数指定した場合、すべての結果が CFDictionaryRef に格納されて戻される。
(例)
{
acct = ddddd;
agrp = test;
class = genp;
pdmn = ak;
svce = "";
"v_Data" = <64>
"v_PersistentRef" = <67656e70 00000000 00000018>;
}
"v_Data" が kSecReturnData に、"v_PersistentRef" が kSecReturnPersistentRef に、それ以外は kSecReturnAttributes が対応している。
2.3 属性値(attributes)
APIの引数 attributes は、新規登録や更新するアイテムの情報を指定する用途で使用する。検索結果も同じ形式で受け取る。
例)CFDictionaryRef attributes
|--kSecClass = kSecClassGenericPassword // パスワードクラスを指定
|--kSecAttrAccount = @"hashiguchi" // アカウント
|--kSecValueData = [@"Jusdf087" dataWithEncoding:NSUTF8StringEncoding] // パスワード
|--kSecAttrDescription = @"Item description"
|--kSecAttrService = @"Service"
|--kSecAttrComment = @"Your comment here."
上記を SecItemAdd の第一引数へ渡すとその内容が Keychain Services へ登録される。保存したい情報(パスワードや証明書)は kSevValueData をキーにして NSData型で渡す。既に同じアカウントが存在する場合は重複エラー(-25299:errSecDuplicateItem)となる。
2.4 クラス
アイテムの種類は kSecClass 属性で指定する。この種類のことをクラスと呼び、クラスは次の5種類が用意されている。
kSecClassGenericPassword | パスワード |
kSecClassInternetPassword | インターネットパスワード |
kSecClassCertificate | 証明書 |
kSecClassKey | 暗号鍵(秘密鍵、公開鍵など) |
kSecClassIdentity | 秘密鍵付き証明書 |
Keychain Services へ格納するアイテムはこのいずれかのクラスに分類する。一般的なアプリケーションでは kSecClassGenericPassword が使われると思われる。
2.5 属性の種類
設定可能な属性の種類は用途に応じて様々なものが用意されている。
Attribute item
これら全てが使われるわけではなく、クラスによって使う属性値が決まっている。例えば kSecClassGenericPassword の場合、次の属性値を扱うことができる。
キー | 意味 | 値の型 |
kSecAttrAccessible | アクセス制約オプション | CFTypeRef |
kSecAttrAccessGroup | アクセスグループ | CFStringRef |
kSecAttrCreationDate | アイテム作成日 | CFStringRef |
kSecAttrModificationDate | アイテム更新日時 | CFStringRef |
kSecAttrDescription | アイテムの説明 | CFStringRef |
kSecAttrComment | コメント | CFStringRef |
kSecAttrCreator | 作成アプリ(4文字) | CFNumberRef |
kSecAttrType | アイテムタイプ(4文字) | CFNumberRef |
kSecAttrLabel | ユーザへ表示する文字列(ラベル) | CFStringRef |
kSecAttrIsInvisible | 不可視属性(利用はアプリ次第) | CFStringRef |
kSecAttrIsNegative | 無効属性(利用はアプリ次第) | CFStringRef |
kSecAttrAccount | アカウント(ログインIDなど) | CFStringRef |
kSecAttrService | サービス名(Application Identifierなど) | CFStringRef |
kSecAttrGeneric | 利用目的が自由な情報 | CFDataRef |
上記は基本的にプログラムで設定する必要がある。例えばアイテム作成日時 kSecAttrCreationDate は自動的に設定される訳ではない。なお kSecAttrAccessGroup は指定がない場合、自動的にデフォルト値が設定される(後述)。
2.6 ユニークキー
検索時にある1つのアイテムを特定するキー(ユニークキー)は kSecClassGenericPassword の場合、次の2つで構成されている
[*1]。
- kSecAttrAccount
- kSecAttrService
[*1] ドキュメントに書かれていたわけではなく調査の結果そう判断した。
kSecAttrService の値が異なれば同じ kSecAttrAccount の値をもつアイテムを登録することができる。また kSecAttrService は登録時に省略することができて、kSecAttrAccount だけで運用することもできる。その場合は同じ kSecAttrAccount の値は複数登録できない(登録時に重複エラーとなる)。
Keychain Services Programming Guide のサンプルなどで設定されている kSecAttrGeneric はユニークキーの構成には入らない。この為、kSecAttrAccount の値が同じで、kSecAttrGeneric の値が異なるアイテムの登録はできない(やはり重複エラーとなる)。ただし検索キーとしては有効に働くので、プログラム内でのキーの用途や分類を表す検索タグのような用途で利用することができる。
2.7 kSecAttrAccessible
kSecAttrAccessible属性を設定することで Keychain Services 内に格納した情報へのアクセスに制限を加えることができる。
キー | アクセス条件 | 推奨用途 | リストアの可否 |
kSecAttrAccessibleAfterFirstUnlock | 再起動後最初のアンロック以降
次の再起動まで | バックグラウンド
アプリケーション | ◯ |
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly | 再起動後最初のアンロック以降
次の再起動まで | バックグラウンド
アプリケーション | × |
kSecAttrAccessibleAlways | なし
(常にアクセス可) | アプリケーションでの
利用は推奨されない | ◯ |
kSecAttrAccessibleAlwaysThisDeviceOnly | なし
(常にアクセス可) | アプリケーションでの
利用は推奨されない | × |
kSecAttrAccessibleWhenUnlocked | デバイスが
アンロックされた状態 | フォアグラウンド
アプリケーション | ◯ |
kSecAttrAccessibleWhenUnlockedThisDeviceOnly | デバイスが
アンロックされた状態 | フォアグラウンド
アプリケーション | × |
- デバイスの状態(ロック、アンロック)の他、PCへバックアップしたデータをリストアする時の対象に含めるかどうかを指定することができる。
- デフォルト(未指定時)は kSecAttrAccessibleWhenUnlocked となっている。バックグラウンドで動作するアプリの場合、ロック状態(スリープ時)にはアクセスできないので注意。ロック状態でも利用したい場合は kSecAttrAccessibleAfterFirstUnlock を明示的に指定する。
2.8 エラーコード
API の戻り値(エラーコード)はリファレンスで定義されている。
Keychain Services Reference
各APIの戻り値は、0 (errSecSuccess) なら正常終了、それ以外はエラー。
3. アクセス制御
3.1 Keychain Access Group
Keychain Services で扱われるアイテムは属性 kSecAttrAccessGroup の値によってアクセスの可否が制御されている。この属性値で示される文字列を Keychain Access Group(以下、グループ)と呼ぶ。アプリケーションはアクセス対象のアイテムと同じグループに所属している必要がある。アプリケーションと異なるグループに所属しているアイテムは検索にヒットしない。例えばあるアイテムの属性 kSecAttrAccessGroup に "GFDZH8DCX.com.xcatsan.keyChainsSample" と設定されている場合、このアイテムへアクセスすることができるアプリケーションはグループ "GFDZH8DCX.com.xcatsan.keyChainsSample" に所属している必要がある。
イメージ:
3.2 アプリケーションの所属グループ
通常アプリケーションは application-identifer で定義されるグループに所属している。application-identifier はデフォルトでは $(AppIdentifierPrefix)$(CFBundleIdentifier) として定義される。$(AppIdentifierPrefix) はプロビジョニングファイルの App Identifier のアスタリスク"*"の左側の英数字、$(CFBundleIdentier)は Info.plist で定義されているアプリケーション識別子を指す。
application-identifier の例
GFDZH8DCX.com.xcatsan.keyChainsSample
プロビジョニングプロファイルの App Identifier の例
アイテムを登録する時にはデフォルトでアプリケーションが所属する application-identifer が kSecAttrAccessGroup に設定される。この為、アプリケーション自身が登録したアイテムを後から読み出すことができる。
なお後述する Entitlements.plistファイルの作成するとこのデフォルト値を変えることができる。
3.3 Keychain Access Group の変更
Keychain Access Group のデフォルト値は application-identifier が使われるが、Entitlements.plistファイル
[*1] を用意することで Keychain Access Group のデフォルト値をアプリケーションで変えることができる。またこのファイルにグループを複数既述することで、アプリケーションが所属するグループを複数定義することができる。以下、plistファイルの作成手順。
[*1] plistファイルの名前は自由に付けることができてリファレンスでは例として keychain-access-groupslplist が上げられていた。ただコード署名の用途などでこのファイルを作成するケースもあり、Keychain Access Group 専用の設定ファイルというわけではない。この為、ここでは独自のファイル名ではなく標準的に使われる Entitlements.plist を使用した。
1. Entitlements.plistファイルの作成
plist 形式のファイルを作成する。作成は手動でも可能だが Xcode の新規作成にテンプレート(Code Signing > Entitlements)が用意されているのでそれを使うのが良いだろう。
テンプレートから作成されたファイルの内容は次の通り。
この中の keychain-access-groups(配列)へ所属するグループを列挙しておく。
2. ファイルの設定
Xcode プロジェクトのターゲットの情報を開き「ビルド」タブ内の「コード署名権限」(変数:CODE_SIGN_ENTITLEMENTS)に 1.で作成したファイル名を指定する。
1. で Xcode のテンプレートから Entitlements.plist を作成した場合、この設定は自動的に行われる。
あとはビルドすればいい。
3.4 Keychain Access Group の命名ルール
Keychain Access Group で指定する文字列には次の命名ルールがある。
プロビジョニングファイルで定義される App Identifier と前方一致する
例えば、App Identifier が "GFDZH8DCX.*" の場合、指定可能なグループ名は次のようになる。
GFDZH8DCX.xcatsan.com.KeychainSample
GFDZH8DCX.xcatsan.com.KeychainSample.temporary
GFDZH8DCX.mikeneko
このルールが満たされない場合はアイテム登録時にエラーとなったり、実機へのインストール時にエラーとなる。
例えば、Entitlements.plist ファイルの keychain-access-groups に上記ルールに合わない文字列を指定した場合、実機へインストールするときに "The executable was signed with invalid entitlements." エラーが表示される。
このルールはセキュリティ上の理由によるもので、これによりプロビジョニングファイルが異なる他のアプリケーションが登録したアイテムの読み出しができなくなっている(シミュレータはこのルールが適用されないので他アプリケーションのアイテムが読み出せている)。
3.5 デフォルトグループの決定ルール
kSecAttrAccessGroup を指定せずに SecItemAdd でアイテムを登録した場合、そのアイテムの kSecAttrAccessGroup に設定される値は次の順番で評価される。
- kSecAttrAccessGroup
- Entitlements.plist/keychain-access-groups
- application-identifier
1. SecItemAdd の第一引数で渡す (CFDictionaryRef)attributes に kSecAttrAccessGroup が設定されている場合は、その値が使われる。
2. 1の設定が無い場合は、ビルド時設定 CODE_SIGN_ENTITLEMENTS で指定されたファイル(Entitlement.plist)に記載された keychain-access-groups 配列の1番目の値が使われる。
3. 1と2が共に設定されていない場合は、application-identifer の値が使われる。application-identifier は $(AppIdentifierPrefix)$(CFBundleIdentifier) として定義される。
3.6 アイテム登録時に指定可能なグループ
SecItemAdd で明示的に kSecAttrAccessGroup を指定する場合、指定できるグループ名は Entitlements.plist ファイルの keychain-access-groups 配列内に含まれるか、もしくは application-identifier と一致する必要がある。このルールに従わない文字列を kSecAttrAccessGroup に設定してアイテムを登録(SecItemAdd)しようとする場合、エラーとなる(コード -25243)。 例えばアプリケーションの application-identifier が "GFDZH8DCX.com.xcatsan.keyChainsSample" にもかかわらず "AADZH1DGX.com.apple.Mail" を指定するとエラーになる。
3.7 シミュレータにおけるグループ
シミュレータの場合は実機と異なり Keychain Access Group は常に "test" となる(Entitlements.plist も無視される)。シミュレータ上で動作するアプリケーションはすべて "test"グループに所属するので、他のアプリケーションが登録したアイテムへアクセスすることができる。
3.8 グループ設定の例
前提
App Identifier: GFDZH8PDDD.*
application-identifier: GFDZH8PDDD.com.yourcompany.KeyChainApp
[1] Entitlements.plist なし
[1]-1 登録時のデフォルトグループ
"GFDZH8PDDD.com.yourcompany.KeyChainApp";
[1]-2 登録時に指定可能なグループ
"GFDZH8PDDD.com.yourcompany.KeyChainApp";
[1]-3 アクセス可能なグループ例
"GFDZH8PDDD.com.yourcompany.KeyChainApp";
[1]-4 アクセス不可なグループ例
"GFDZH8PDDD.";
"GFDZH8PDDD.com.yourcompany.OtherApp";
"XJ7GS56DBA.com.apple.MailApp";
(解説)Entitlements.plist を用意しない一般的なパターン。この場合は application-identifier が登録時のデフォルトグループとなる。またアクセスできるのは application-identifier のみ。
[2] Entitlements.plist あり
keychain-access-groups:
GFDZH8PDDD.private
GFDZH8PDDD.com.yourcompany.share
[2]-1 登録時のデフォルトグループ
"GFDZH8PDDD.private";
[2]-2 登録時に指定可能なグループ
"GFDZH8PDDD.private"
"GFDZH8PDDD.com.yourcompany.share"
"GFDZH8PDDD.com.yourcompany.KeyChainApp"
[2]-3 アクセス可能なグループ例
"GFDZH8PDDD.private";
"GFDZH8PDDD.com.yourcompany.share";
"GFDZH8PDDD.com.yourcompany.KeyChainApp";
[2]-4 アクセス不可なグループ例
"GFDZH8PDDD.other";
"GFDZH8PDDD.com.yourcompany.OtherApp";
"XJ7GS56DBA.com.apple.MailApp";
(解説)application-identifier は必ずアクセス可能なグループに入る(ただしデフォルト決定時の優先順位は一番低い)。
4. 開発・運用情報
4.1 API 利用方法
API を利用するにあたっては Xcode のプロジェクトに Security.framework を追加する。
また Security/Scurity.h をインポートしておく。
4.2 実機とシミュレータでの違い
- 実機の場合は Keychain Service リファレンスの説明の通り他アプリの情報は読み出せない。しかしシミュレータの場合はこの制限がなく読み出しが可能となっている。たとえば kSecClassGenericPassword だけを条件にして SecItemCopyMatching で検索をかけるとそのアプリ以外で登録された情報も取得できる。
- シミュレータの場合、kSecAttrAccessGroup の設定はできない。常に "test" となる(だから上記の様な挙動となる)。実機の場合はデフォルトで $(AppIdentifierPrefix)$(CFBundleIdentifier) となる。
(例)GFDZH8DCX.com.xcatsan.keyChainsSample
4.3 アプリを削除した場合の挙動
Keychain Service に登録された情報は、実機・シミュレータ共にアプリを削除しても残る。再びアプリをインストールすると以前の情報を読み出すことが出来る。
5. サンプル
Keychain Services へ ID、パスワードの組を保存、更新、検索、削除するサンプルを作ってみた。
結果はデバッグコンソールに表示される。dump ボタンを押すと現在登録したすべてのアイテム情報を表示する。
ソースコードは理解しやすさを優先させてわざと冗長にしてある。以下にそれぞれの操作の処理を見ていく。
5.1 登録・更新
最初にログインIDをキーにして登録済みかどうかチェックする。登録済みの場合はパスワードのみ SecItemUpdate で更新する。登録がまだの場合は SecItemAdd を使い新規に登録する。
- (IBAction)update:(id)sender
{
NSMutableDictionary* attributes = nil;
NSMutableDictionary* query = [NSMutableDictionary dictionary];
NSData* passwordData = [self.password.text dataUsingEncoding:NSUTF8StringEncoding];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:(id)self.loginId.text forKey:(id)kSecAttrAccount];
[query setObject:SERVICE_NAME forKey:(id)kSecAttrService];
[query setObject:[IDENTIFIER dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecAttrGeneric];
OSStatus err = SecItemCopyMatching((CFDictionaryRef)query, NULL);
if (err == noErr) {
// update item
NSLog(@"SecItemCopyMatching: noErr");
attributes = [NSMutableDictionary dictionary];
[attributes setObject:passwordData forKey:(id)kSecValueData];
[attributes setObject:[NSDate date] forKey:(id)kSecAttrModificationDate];
err = SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attributes);
if (err == noErr) {
NSLog(@"SecItemUpdate: noErr");
} else {
NSLog(@"SecItemUpdate: error(%d)", err);
}
} else if (err = errSecItemNotFound) {
// add new item
NSLog(@"SecItemCopyMatching: errSecItemNotFound");
attributes = [NSMutableDictionary dictionary];
[attributes setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[attributes setObject:(id)self.loginId.text forKey:(id)kSecAttrAccount];
[attributes setObject:passwordData forKey:(id)kSecValueData];
err = SecItemAdd((CFDictionaryRef)attributes, NULL);
if (err == noErr) {
NSLog(@"SecItemAdd: noErr");
} else {
NSLog(@"SecItemAdd: error(%d)", err);
}
} else {
NSLog(@"SecItemCopyMatching: error(%d)", err);
}
}
配列を渡すこともできるので一度に複数のアイテムを登録することもできる。
5.2 削除
削除したいアイテムの条件を queryに設定し、SecItemDelete へ渡す。
- (IBAction)delete:(id)sender
{
NSMutableDictionary* query = [NSMutableDictionary dictionary];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:(id)self.loginId.text forKey:(id)kSecAttrAccount];
OSStatus err = SecItemDelete((CFDictionaryRef)query);
if (err == noErr) {
NSLog(@"SecItemDelete: noErr");
} else {
NSLog(@"SecItemDelete: error(%d)", err);
}
}
query に一致するアイテムはすべて(複数)削除される。
5.3 検索(パスワード取得)
query に条件を用意し SecItemCopyMatching へ渡して結果を取得する。
- (IBAction)getPassword:(id)sender
{
NSMutableDictionary* query = [NSMutableDictionary dictionary];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:(id)self.loginId.text forKey:(id)kSecAttrAccount];
[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
NSData* passwordData = nil;
OSStatus err = SecItemCopyMatching((CFDictionaryRef)query,
(CFTypeRef*)&passwordData);
if (err == noErr) {
NSLog(@"SecItemCopyMatching: noErr");
self.password.text = [[[NSString alloc] initWithData:passwordData
encoding:NSUTF8StringEncoding] autorelease];
} else if(err = errSecItemNotFound) {
NSLog(@"SecItemCopyMatching: errSecItemNotFound");
} else {
NSLog(@"SecItemCopyMatching: error(%d)", err);
}
}
5.4 検索(全アイテム取得)
SecItemCopyMatching の query に kSecMatchLimitAll をセットすると条件に合うすべてのアイテムを取得することができる。条件はクラスのみを指定している。またこの場合の戻り値 result の型は CFArray となる。
- (IBAction)dump:(id)sender
{
NSMutableDictionary* query = [NSMutableDictionary dictionary];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
[query setObject:(id)kSecMatchLimitAll forKey:(id)kSecMatchLimit];
CFArrayRef result = nil;
OSStatus err = SecItemCopyMatching((CFDictionaryRef)query,(CFTypeRef*)&result);
if (err == noErr) {
NSLog(@"SecItemCopyMatching: noErr");
NSLog(@"%@", result);
} else if(err = errSecItemNotFound) {
NSLog(@"SecItemCopyMatching: errSecItemNotFound");
} else {
NSLog(@"SecItemCopyMatching: error(%d)", err);
}
}
下記は出力例(実機 iOS4.2.1/3GS)。このような dumpメソッドを用意しておくとデバッグの時に役立つ。
KeyChainsSampe[4108:307] (
{
acct = hashi;
agrp = "GFDZH8DCX.com.xcatsan.keyChainsSample";
pdmn = ak;
svce = "";
},
{
acct = mikeneko;
agrp = "GFDZH8DCX.com.xcatsan.keyChainsSample";
pdmn = ak;
svce = "";
},
{
acct = hashi;
agrp = "GFDZH8DCX.com.xcatsan.keyChainsSample";
cdat = "2011-02-04 05:14:45 +0000";
desc = "This is a password";
gena = <70617373 776f7264>;
mdat = "2011-02-04 05:14:45 +0000";
pdmn = ak;
svce = SampleService;
}
)
キーは4文字に短縮された文字列が使われている。シンボルとの対応は当投稿の付録を参照のこと。
6. ソースコード
GitHub からどうぞ。
KeyChainsSampe at 2011-02-06 from xcatsan/iOS-Sample-Code - GitHub
7. 運用上の注意点
Keychain Services を運用するにあたっては次の点に注意する必要がある。
プロビジョニングファイルを変更しない
「3. アクセス制御」で説明したように、Keychain Services 内のアイテムへのアクセス可否を決める Keychain Access Group はプロビジョニングファイル内の App Identifier に依存する。異なる App Identifier で登録されたアイテムへはアクセスすることができない。バージョンアップ時にプロビジョニングファイルを変えた場合、App Identifier が変わると前のバージョンで登録したアイテムが新しいバージョンでは利用できない、といった問題が起こる。
バックグラウンドアプリケーションは適切な kSecAttrAccessible を設定する
「2. 利用方法 - kSecAttrAccessbile」で説明したように、デフォルトでは iOSデバイスのロックが外れた状態(つまりユーザが操作している時)でしかアイテムへアクセスできないようになっている。ロック中にアイテムへアクセスするようなバックグラウンドアプリケーションの場合、アイテムの kSecAttrAccesible 属性の値を kSecAttrAccessibleAfterFirstUnlock(もしくは kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)に設定しておく必要がある。
8. 参考情報
Keychain Services Programming Guide: Introduction
Keychain Service の概要、サンプルコードなど。
Keychain Services Reference
SecItem系関数リファレンス。
Keychain Services Reference
Mac OS X 用 Keychain Service Reference(SecItem系関数、SecKeychain系関数のリファレンス)。
Simple iPhone Keychain Access - Blog - Use Your Loaf
SecItem系関数の使い方が解説されている。
(旧) Cocoaの日々: Keychain Services 調査 (1) 情報収集
過去に Mac OS X で調査した時の記録など(二十数回続く連載)。
ロックされたiPhoneが6分でハッキング…スマートフォンはもはや電話じゃないことを忘れずに:国内・海外情報から見える『企業のWEB活用法』:ITmedia オルタナティブ・ブログ
jailbreak された iPhone なら専用ツールを使い Keychain Services内のデータが読み出すことができるらしい。最初に触れたように iOS の場合、Keychain Services用のパスワードは(ユーザではなく)iOS 自身が管理する為、例えばそれが格納された場所が特定できればパスワードを解析することは難しくないかもしれない(そんなすぐに見つかる/アクセスできるような配置はしていないとは思うが。でも rootが取られればどうだろう。)。
9. 付録
属性キーの文字列
属性キーのシンボルと実際の文字列との対応表。
kSecAttrAccessible | pdmn |
kSecAttrCreationDate | cdat |
kSecAttrModificationDate | mdat |
kSecAttrDescription | desc |
kSecAttrComment | icmt |
kSecAttrCreator | crtr |
kSecAttrType | type |
kSecAttrLabel | labl |
kSecAttrIsInvisible | invi |
kSecAttrIsNegative | nega |
kSecAttrAccount | acct |
kSecAttrService | svce |
kSecAttrGeneric | gena |
kSecAttrSecurityDomain | sdmn |
kSecAttrServer | srvr |
kSecAttrProtocol | ptcl |
kSecAttrAuthenticationType | atyp |
kSecAttrPort | port |
kSecAttrPath | path |
kSecAttrSubject | subj |
kSecAttrIssuer | issr |
kSecAttrSerialNumber | slnr |
kSecAttrSubjectKeyID | skid |
kSecAttrPublicKeyHash | pkhh |
kSecAttrCertificateType | ctyp |
kSecAttrCertificateEncoding | cenc |
kSecAttrKeyClass | kcls |
kSecAttrApplicationLabel | klbl |
kSecAttrIsPermanent | perm |
kSecAttrApplicationTag | atag |
kSecAttrKeyType | type |
kSecAttrKeySizeInBits | bsiz |
kSecAttrEffectiveKeySize | esiz |
kSecAttrCanEncrypt | encr |
kSecAttrCanDecrypt | decr |
kSecAttrCanDerive | drve |
kSecAttrCanSign | sign |
kSecAttrCanVerify | vrfy |
kSecAttrCanWrap | wrap |
kSecAttrCanUnwrap | unwp |
kSecAttrAccessGroup | agrp |
kSecAttrAccessible の文字列
kSecAttrAccessibleAfterFirstUnlock | ck |
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly | cku |
kSecAttrAccessibleAlways | dk |
kSecAttrAccessibleAlwaysThisDeviceOnly | dku |
kSecAttrAccessibleWhenUnlocked | ak |
kSecAttrAccessibleWhenUnlockedThisDeviceOnly | aku |
- - - -
ここまで読んだ方の中で Keychain Services がアプリ間の情報共有の用途に使えることに気がついた人がいるかもしれない。答えは半分イエスで半分ノー(制限がある)。次回その方法について解説する。