前回紹介した 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"(固定)となる。
nishi
2013年7月28日 0:52
>同じプロビジョニングファイルから作成されたアプリケーション間でしか
>Keychain Services を介したデータ共有は行えない
同じAppIdentifierPrefixでなければデータ共有は行えないの間違いではないでしょうか。
AppIdentifierPrefixが同じであれば、別のプロビジョニングファイルでもよいと思います。
nishi
2013年7月28日 0:52
>同じプロビジョニングファイルから作成されたアプリケーション間でしか
>Keychain Services を介したデータ共有は行えない
同じAppIdentifierPrefixでなければデータ共有は行えないの間違いではないでしょうか。
AppIdentifierPrefixが同じであれば、別のプロビジョニングファイルでもよいと思います。