[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日間)