踊る犬.netブログ (旧)

[iOS] アカウント情報をアプリ間で安全に共有する方法

はじめに

Google Drive, Docs, SheetsのiOS版では、アプリ間でGoogleアカウント情報を共有しています。
いずれかのアプリで一度認証すれば、他のアプリでは面倒な認証手順が不要になります。
これはどういう仕組みで行われているのでしょう?
アプリはどのようにして、ユーザの入力無しにユーザを識別しているのでしょう。

Keychain:安全に機密情報を取り扱う仕組み

実は、先に述べたGoogleのアプリ達は、KeychainというOSの仕組みを使っています。
Keychainは、もしあなたがMac OSXユーザであればなじみ深いと思います。実はiOSにも備わっています。
これは、パスワードや秘密鍵、証明書などの機密情報を安全に管理するための仕組みです。
Keychainで保管されるデータは暗号化されます。

アカウント情報の取り扱いに最適

Keychainと同じようにユーザ情報を保存するための仕組みとして、UserDefaultsというものがあります。
UserDefaultsは、主にプリファレンスなどを保存するための仕組みです。
UserDefaultsと比べて、Keychainが機密情報の取り扱いに優れている点は以下です:

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では、それぞれ以下の定数で指定できます。

以下のように指定します。

LUKeychainAccess* keychainAccess = [LUKeychainAccess standardKeychainAccess];
keychainAccess.accessibilityState = LUKeychainAccessAttrAccessibleWhenUnlocked;

Keychain Access Group

「どの」アプリケーションがアイテムにアクセスできるのかを制御します。
デフォルトでは、アクセスできるアプリケーションはそのアイテムを格納したアプリケーション自身だけです。
グループの指定によって、同じグループに属するアプリケーションからのアクセスを許可出来ます。
このグループは、アイテムごとに指定できます。

例えば、あなたのapplication-identifiercom.foobar.applicationだとします。
あなたのアプリケーションが読み書き可能なグループは、そのグループ識別子がapplication-identifier前方一致する場合です。
グループによる権限の違いは以下の通りです。

つまり、com.foobarというグループに属するアイテムを作成すると、アクセス出来るアプリケーションはapplication-identifiercom.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"];

簡単ですね!
本家にはこの拡張をプルリクエストしておきました。