Demo先行:https://github.com/rayshen/GIKeychainGroupDemo
該demo里有2個工程,你先運行任何一個會存儲一個值,再運行另一個會訪問之前的app存儲的值,並修改。
官方:https://developer.apple.com/library/ios/samplecode/GenericKeychain/Introduction/Intro.html
之前博客使用過Keychain,實現了數據刪除APP后還能保存,但是並沒有實現APP間的共享。
實現APP間的數據共享,主要依賴於在數據存入鑰匙串時,使用同一個鑰匙串條目。
主要分為兩部分:
1.賦予應用對某個鑰匙串條目的訪問權限。
2.寫入時配置鑰匙串條目,對kSecAttrAccessGroup的值進行設置。
一、APP對鑰匙串的訪問權限:
(1)未對應用APP的entitlement(授權)進行配置時,APP使用鑰匙串存儲時,會默認存儲在自身BundleID的條目下。

(2)對APP的entitlement(授權)進行配置后,說明APP有了對某個條目的訪問權限。

鑰匙串的可視化效果可參見Mac的APP-鑰匙串訪問。
APP鑰匙串訪問權限的配置方法:(這里XXXXX模擬器隨意,但真機必須為自己開發者賬號ID,否則無法通過編譯)
1.新建一個Plist文件,在Plist中的數組中添加可以訪問的條目的名字(如KeychainAccessGroups.plist),結構如下:

Plist代碼:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>keychain-access-groups</key> <array> <string>XXXXX.GrassInfoAppFamily</string> </array> </dict> </plist>
2.在Build-setting中進行配置,搜索entitlement,注意路徑別配置錯:

二、APP對鑰匙串的操作:
鑰匙串的操作接口都位於Security.framework框架下,它是一個sqlite數據庫,位於/private/var/Keychains/keychain-2.db,其保存的所有數據都是加密過的。
其過程可以總結為:
1.配置查詢字典,格式是NSMutableDictionary,需要配置的內容下次再分析,功能就相當於寫一句SQL一樣。
2.進行增(SecItemAdd)、刪(SecItemDelete)、改(SecItemUpdate)、查(SecItemCopyMatching)。
代碼Demo里面有,這里以增為例,下面有2個語句,一個是增加到自身BundleID的鑰匙串條目,一個是增加到共享的條目中。
//創建一個基本的查詢字典 + (NSMutableDictionary *)getKeychainQuery:(NSString *)service { return [NSMutableDictionary dictionaryWithObjectsAndKeys: (__bridge id)kSecClassGenericPassword,(__bridge id)kSecClass, service, (__bridge id)kSecAttrService, service, (__bridge id)kSecAttrAccount, (__bridge id)kSecAttrAccessibleAfterFirstUnlock,(__bridge id)kSecAttrAccessible, nil]; } + (void)addKeychainData:(id)data forKey:(NSString *)key{ //Get search dictionary NSMutableDictionary *keychainQuery = [self getKeychainQuery:key]; //Delete old item before add new item SecItemDelete((__bridge CFDictionaryRef)keychainQuery); //Add new object to search dictionary(Attention:the data format) [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData]; //Add item to keychain with the search dictionary SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL); } +(void)addShareKeyChainData:(id)data forKey:(NSString *)key{ //Get search dictionary NSMutableDictionary *keychainQuery = [self getKeychainQuery:key]; [keychainQuery setObject:accessGroupItem forKey:(id)kSecAttrAccessGroup]; //Delete old item before add new item SecItemDelete((__bridge CFDictionaryRef)keychainQuery); //Add new object to search dictionary(Attention:the data format) [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData]; //Add item to keychain with the search dictionary SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL); }
函數 [keychainQuery setObject:accessGroupItem forKey:(id)kSecAttrAccessGroup] 的配置,就是指定了這次寫入時的鑰匙串條目,不寫入時默認為plist配置文件里第一個條目。
在查詢中,也可以對查詢的鑰匙串條目進行配置,默認會對所有有權限的條目進行搜索。
三、keychain的組成:
參考博客:http://my.oschina.net/w11h22j33/blog/206713

每一個keyChain的組成如圖,整體是一個字典結構.
1.kSecClass key 定義屬於那一種類型的keyChain
2.不同的類型包含不同的Attributes,這些attributes定義了這個item的具體信息
3.每個item可以包含一個密碼項來存儲對應的密碼
對於最常用密碼類型,我們應該如下配置
[wrapper setObject:kSecClassGenericPassword forKey:(id)kSecClass];//class
[wrapper setObject:@"username" forKey:(id)kSecAttrAccount];//key
[wrapper setObject:@"password"forKey:(id)kSecValueData];//value
[wrapper setObject:(id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(id)kSecAttrAccessible];
kSecAttrAccessiblein變量用來指定這個應用合適需要訪問這個數據。我們需要對這個選項特別注意,並且使用最嚴格的選項。這個鍵(key)可以設置6種值。
你可以參考以下:

四、安全
前面說到,APP能夠訪問的keychain數據是通過其entitlements文件指定的。
但是!!如果使用帶有一個*通配符的entitlments,因此它能夠訪問keychain中的所有條目。。或者,如果用一個包含所有訪問組(access group)的entitlements文件,也能夠訪問所有的keychain數據。
但以上僅限模擬器,在真機調試時,假如你plist里的條目,和自己調試文件(pro file)的ID不一致時(比如:EC0880A1.company),進行真機編譯的時候是會報錯的。
所以真正要保證全家桶,還需要保證這些APP在同一個開發者ID下才行。
前面關於在未配置keychain-access-group的情況下,我參考別人說是默認存儲在該BundleID的條目下,但實測並非存儲在該條目下。具體為什么我也暫時不清楚。但是可以肯定的是,在iOS的沙盒機制中,你默認存儲的條目下的K-V,其他APP是無法訪問的(除了模擬器中的通配符*,真機是不行的)。
關於Keychain還有很多值得挖掘的,比如具體在sqlite數據庫的表單存儲方式等,有錯歡迎指正。
