はじめに
Google Drive, Docs, SheetsのiOS版では、アプリ間でGoogleアカウント情報を共有しています。
いずれかのアプリで一度認証すれば、他のアプリでは面倒な認証手順が不要になります。
これはどういう仕組みで行われているのでしょう?
アプリはどのようにして、ユーザの入力無しにユーザを識別しているのでしょう。
Keychain:安全に機密情報を取り扱う仕組み
実は、先に述べたGoogleのアプリ達は、KeychainというOSの仕組みを使っています。
Keychainは、もしあなたがMac OSXユーザであればなじみ深いと思います。実はiOSにも備わっています。
これは、パスワードや秘密鍵、証明書などの機密情報を安全に管理するための仕組みです。
Keychainで保管されるデータは暗号化されます。
アカウント情報の取り扱いに最適
Keychainと同じようにユーザ情報を保存するための仕組みとして、UserDefaultsというものがあります。
UserDefaultsは、主にプリファレンスなどを保存するための仕組みです。
UserDefaultsと比べて、Keychainが機密情報の取り扱いに優れている点は以下です:
- アプリが削除されてもデータが残る
- 同じ
Keychain Access Group
に属するアプリ同士でアクセスできる (Keychain Access Group
は後ほど詳しく説明します) - データが暗号化される
Keychain Servicesとは
Keychain Servicesは、Keychainを使うためのAPI群です。
以下からAppleのドキュメントを参照できます:
Mac OSXとiOSではAPIが異なるので注意です。
ここでは、iOSでの使用を前提として説明します。
日本語の資料では以下が参考になります。
しかしながら上記ページは古くて、XCode上の手順が有効でない部分があります。
本ページでは、XCode 5で設定する場合を説明します。
Keychain Servicesの基本的な使い方
Framework
利用するには、Security.framework
をプロジェクトに追加してください。
便利なラッパモジュールを使おう
Appleが用意したAPIをそのまま使うのもいいですが、我々には時間がありません。
オススメは LUKeychainAccess です。LUKeychainAccess
を使えば、NSUserDefaults
と同じ感覚でKeychainのアイテムを管理できます。
以降は、このラッパを使う前提で説明します。
基本的な使い方
NSUserDefaults
と全く同じです。
[[LUKeychainAccess standardKeychainAccess] setBool:NO forKey:@"authorized"];
BOOL authorized = [[LUKeychainAccess standardKeychainAccess] boolForKey:@"authorized"];
Keychainでのアクセス制御
制御の軸はアイテムに「いつ」アクセスできるのかと、「どれが」アクセスできるのか、の2つがあります。
Accessible属性
アプリケーションが「いつ」アクセスできるのかを制御します。詳しくはこちら。LUKeychainAccess
では、それぞれ以下の定数で指定できます。
- LUKeychainAccessAttrAccessibleAfterFirstUnlock
- 再起動後最初のアンロック以降/次の再起動まで
- LUKeychainAccessAttrAccessibleAfterFirstUnlockThisDeviceOnly
- 再起動後最初のアンロック以降/次の再起動まで
- LUKeychainAccessAttrAccessibleAlways
- 常にアクセス可
- LUKeychainAccessAttrAccessibleAlwaysThisDeviceOnly
- 常にアクセス可
- LUKeychainAccessAttrAccessibleWhenUnlocked
- デバイスがアンロックされた状態
- LUKeychainAccessAttrAccessibleWhenUnlockedThisDeviceOnly
- デバイスがアンロックされた状態
以下のように指定します。
LUKeychainAccess* keychainAccess = [LUKeychainAccess standardKeychainAccess];
keychainAccess.accessibilityState = LUKeychainAccessAttrAccessibleWhenUnlocked;
Keychain Access Group
「どの」アプリケーションがアイテムにアクセスできるのかを制御します。
デフォルトでは、アクセスできるアプリケーションはそのアイテムを格納したアプリケーション自身だけです。
グループの指定によって、同じグループに属するアプリケーションからのアクセスを許可出来ます。
このグループは、アイテムごとに指定できます。
例えば、あなたのapplication-identifier
がcom.foobar.application
だとします。
あなたのアプリケーションが読み書き可能なグループは、そのグループ識別子がapplication-identifier
に前方一致する場合です。
グループによる権限の違いは以下の通りです。
- アクセス可なアイテムのグループ
com.foobar.application
com.foobar
- アクセス出来ないアイテムのグループ
com.apple.calendar
com.foobar.mailer
つまり、com.foobar
というグループに属するアイテムを作成すると、アクセス出来るアプリケーションはapplication-identifier
がcom.foobar.*
に相当するものになります。
また、注意点ですが、同じグループに属していてもアクセスできない場合があります。
それは、Code Signing Identity
がアプリケーション間で異なる場合です。
つまり、アイテムを共有するアプリケーションは同一ベンダーのものでなければなりません。
Keychain Access Groupの変更
Keychain Access Groupをデフォルトのapplication-identifier
から変更するには、Entitlementsを作成します。
XCode 5ではこの作成手順が簡単になっています。
まず、ビルドターゲットのCapabilitiesタブを開きます。
Keychain Sharingという項目を見つけたら、スイッチをONにしましょう。
Keychain Groupsというリストが現れたはずです。
ここに、指定したいグループの識別子を入力します。
グループは複数指定できます。
一番上に記述したものが、デフォルトで使用されるグループになります。
シミュレータにおけるグループの取り扱い
シミュレータでも動作しますが、テストは実機でやる事を強くおすすめします。
というのは、シミュレータだと常にグループがtest
となるからです。
シミュレータ上では、グループ指定に関わらず全てのアプリケーションから全てのアイテムにアクセス出来ます。
TestFlight用ビルドとデバッグ用ビルド間ではアイテムを共有出来ない
ここで、TestFlight経由でインストールしたビルドと、USB経由でインストールしたビルドのapplication-identifier
を別々にすればテストできるかもしれないと思いますが、これも無理です。
理由は、Code Signing Identity
が異なるからです。
共通のCode Signing Identity
で、異なるapplication-identifier
のアプリを用意しましょう。
アイテムのキー
基本的な使い方のセクションで説明した通り、アイテムのキーは単純な文字列で指定できます。
[[LUKeychainAccess standardKeychainAccess] setBool:NO forKey:@"authorized"];
これは以下のような単純なkey/valueストアです。
key = value
Keychain Servicesでは、この単純なキーに加えて、オプションでサービス名も付けられます。
以下のようにserviceディレクトリにkey/valueを格納していくイメージです。
service.key = value
サービス名の指定は、TwitterやFacebookの認証情報をセットにして格納したい場合に便利です。
サービス名つきアイテムへのアクセス方法
残念ながらLUKeychainAccess
は、サービス名の指定にまだ対応していません。。
そこで、このラッパをフォークして対応させてみました。
サービス名対応版なら、以下のようにサービス名を指定できます。
[[LUKeychainAccess standardKeychainAccess] setBool:NO forKey:@"authorized" service:@"Twitter"];
BOOL authorized = [[LUKeychainAccess standardKeychainAccess] boolForKey:@"authorized" service:@"Twitter"];
簡単ですね!
本家にはこの拡張をプルリクエストしておきました。