[iOS] 複数アプリケーション間でのデータ共有 〜 Keychain Services を使った第三の方法

2011年2月7日月曜日 | Published in , , | 1 コメント

※タイトルはあまり深い意味は無い。なんとなく「第三の〜」の響きが良かったので。。

前回紹介した Keychain Services を使えば制限付きながら iOS 上の複数のアプリケーションでデータ共有ができることがわかったのでそれを解説する。

[前回] Cocoaの日々: [iOS] Keychain Services とは


仕組み


Keychain Services に格納されるアイテム(パスワードなど)のアクセス制御は Keychain Access Group(グループ)を元に行われる。アイテムにはこのグループ属性があり、同じグループに所属しているアプリケーションからのみアクセスが許可される。

アプリケーションは複数のグループに所属することができるので、データの共有を目的したグループを用意しておき、複数のアプリケーションでこのグループに所属すれば、このグループに所属するアイテムへそれら複数のアプリケーションがアクセスすることができる。

Keychain Services のアイテムはパスワード、秘密鍵、証明書を格納するようになっているが、CFData(NSData)型 であればパスワードである必要はない。格納したい値を CFData(NSData)へ変換すれば Keychain Services へ格納することができるので任意の値を共有することができる。

Keychain Access Group の詳細は前回の「3. アクセス制御」を参照のこと。


サンプル


プロジェクトを2つ(KeyChainApp-1と KeyChainApp-2)用意し、それぞれのアプリケーションから同じアイテムへアクセスできるかを検証してみた。


KeyChainApp-1


Entitlements.plist

application-identifier: GFDZH8PXXX.com.yourcompany.KeyChainApp-1

アイテム登録コード
- (IBAction)addNewItem
{
 NSData* passwordData = [self.password.text dataUsingEncoding:NSUTF8StringEncoding];
 NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
 [attributes setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
 [attributes setObject:(id)self.account.text forKey:(id)kSecAttrAccount];
 [attributes setObject:passwordData forKey:(id)kSecValueData];
 [attributes setObject:@"GFDZH8PXXX.share" forKey:(id)kSecAttrAccessGroup];
 OSStatus err = SecItemAdd((CFDictionaryRef)attributes, NULL);
 if (err == noErr) {
 NSLog(@"SecItemAdd: noErr");
 } else {
 NSLog(@"SecItemAdd: error(%d)", err);
 }
}
登録するアイテムの Keychain Access Group (kSecAttrAccessGroup)に "GFDZH8PXXX.share" を指定している。
※サンプルコードを自分の環境でビルドする場合は "GFDZH8PXX" の箇所を自分のプロビジョニングファイルの app-identifier に書き換えること。

KeyChainApp-2


Entitlements.plist

application-identifier: GFDZH8PXXX.com.yourcompany.KeyChainApp-2

アクセス可能なアイテムをすべてデバッグコンソールへ表示。
- (IBAction)dumpItems
{
 NSMutableDictionary* query = [NSMutableDictionary dictionary];
 
 [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
 [query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
 [query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
 [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);
 }
}
パスワードを更新。
- (IBAction)updateItem
{
 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.account.text forKey:(id)kSecAttrAccount];
 
 OSStatus err = SecItemCopyMatching((CFDictionaryRef)query, NULL);
 
 if (err == noErr) {
 // update item
 NSLog(@"SecItemCopyMatching: noErr");
 
 attributes = [NSMutableDictionary dictionary];
 [attributes setObject:passwordData forKey:(id)kSecValueData];
 
 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");
 
 } else {
 NSLog(@"SecItemCopyMatching: error(%d)", err);
 }
 
}


結果


まず KeyChainApp-1 を立ち上げてアカウント/パスワードを登録(Add new item)する。
Dump items で登録内容を確認しておく。
{
 acct = hashiguchi;
 agrp = "GFDZH8PCUM.share";
 pdmn = ak;
 svce = "";
 "v_Data" = <70617373 3030>;
 }
続いて KeyChainApp-2 を立ち上げ、登録アイテムを表示する(Dump items)。

すると
{
 acct = hashiguchi;
 agrp = "GFDZH8PCUM.share";
 pdmn = ak;
 svce = "";
 "v_Data" = <70617373 3030>;
 }
出た。KeyChainApp-1 で登録したデータをまったく別のアプリ KeyChainApp-2 で読み出すことができた。

更新はどうだろうか。KeyChainApp-1 で登録したこのデータ(パスワード)を KeyChainApp-2 で書き換えてみる。
すると
{
 acct = hashiguchi;
 agrp = "GFDZH8PCUM.share";
 pdmn = ak;
 svce = "";
 "v_Data" = <70617373 3131>;
 }
書き換わった。


ソースコード


GitHub からどうぞ。
KeyChainApp-1 at 2011年02月07日 from xcatsan/iOS-Sample-Code - GitHub
KeyChainApp-2 at 2011年02月07日 from xcatsan/iOS-Sample-Code - GitHub


制限


複数アプリケーション間でのデータ共有方法として使えることがわかった Keychain Services だが重大な制限もある
同じプロビジョニングファイルから作成されたアプリケーション間でしか
Keychain Services を介したデータ共有は行えない
これは Keychain Access Group の指定方法の制約による。詳細は前回の「3. アクセス制御」を参照のこと。この為、残念ながら他社の作成したアプリとデータ交換を自由に行えるわけではない。


補足


シミュレータの場合、Entitlements.plist は無視されるので今回のように kSecAttrAccessGroup を設定すると登録はエラーとなる(-25243)。シミュレータの場合、前回説明したように Keychain Access Group は常に "test"(固定)となる。

コメント

  1. nishi
    2013年7月28日 0:52

    >同じプロビジョニングファイルから作成されたアプリケーション間でしか
    >Keychain Services を介したデータ共有は行えない
    同じAppIdentifierPrefixでなければデータ共有は行えないの間違いではないでしょうか。
    AppIdentifierPrefixが同じであれば、別のプロビジョニングファイルでもよいと思います。

  2. nishi
    2013年7月28日 0:52

    >同じプロビジョニングファイルから作成されたアプリケーション間でしか
    >Keychain Services を介したデータ共有は行えない
    同じAppIdentifierPrefixでなければデータ共有は行えないの間違いではないでしょうか。
    AppIdentifierPrefixが同じであれば、別のプロビジョニングファイルでもよいと思います。

Leave a Response

[フレーム]

人気の投稿(過去 30日間)

  • 公式リファレンスの Q&A に解説がある。 Technical Q&A QA1551: Detecting the start and end edit sessions of a cell in NSTableView. 方法は Delegate と Not...
  • 2011年06月09日 追記  UITableViewCell の Identifier 設定を忘れてたので追記しました。 UINib を使うと簡単に Nib で定義した UITableViewCell が使える。 今回のサンプル: [関連] Cocoaの日々: [iO...
  • Asset Catalogには画像以外のデータも置ける。サウンドファイル(.aif)を置いてみた。 取り出すには NSDataAsset を使う。 let sound = NSDataAsset(name: name) // use sound.data 取り出したサウ...
  • パスワードを暗号化して安全に iPhone/iPad へ保管したい。iOS はこの用途の為に Keychain Services を提供している。今回は Keychain Services について調べてみた。リファレンスの内容に加え、独自に調査・検証した結果をまとめてある。動作...
  • Core Data を使ったアプリケーションで下のような検索機能を実装している。 設定された値を元に NSPredicate を作成し、Core Data に対して検索をかけるのだが、こういう場合に NSCompoundPredicate が役に立つ。 NSCompound...

AltStyle によって変換されたページ (->オリジナル) /