我知道没Demo看博客很痛苦,所以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钥匙串访问权限的配置方法:
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] 的配置,就是指定了这次写入时的钥匙串条目,不写入时默认会写入自身BundleID的条目下
在查询中,也可以对查询的钥匙串沙盒进行配置,默认会对所有有权限的条目进行搜索。
三、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文件中的声明了别人的BundleID,那么那个人默认存的钥匙串条目就能被你访问到……(我受到了惊吓。。)
但假如你的应用带*通配符的entitlments能不能上架就不得而知了……
因为说明,这钥匙串不是安全的,要存钥匙串,还得加密啊同学们。