iOS keyChain 研究

一.基本知识

1.方法

SecItemAdd 
SecItemUpdate
SecItemDelete
SecItemCopyMatching


2.权限 

文档上说iOS的keyChain是一个相对独立的空间,当程序替换,删除时并不会删除keyChain的内容,这个要比Library/Cache好。刷机,恢复出厂应该就没有了。关于备份,只会备份数据,到那时不会备份设备的密钥,换句话说,即使拿到数据,也没有办法解密里面的内容。有人说似乎破解的手机就能破解keyChain,本人并不清楚,希望有大神能指教。但个人认为,keyChain只是沙盒的升级版,可以存放一些非私密的信息,即使破解也不影响其它用户,只影响那个破解了的设备。(比如针对该设备的一个密钥)。

可访问性一般来说,自己的程序只能访问自己的keychain,相同bundle的程序通过设置group可以互相共享同组的keychain,从而实现程序间可以共同访问一些数据。详细后面介绍一些我测试下来的经验。


3.如何查询keyChain

[objc]  view plain copy print ?
  1. genericPasswordQuery = [[NSMutableDictionary alloc] init];   
  2. [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];//1  
  3. [genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];//2  
  4. if (accessGroup != nil){  
  5.     [genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];//3  
  6. }  
  7. [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];//4  
  8. [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];//5  
  9. NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];  
  10. NSMutableDictionary *outDictionary = nil;      
  11. if (SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr){//6  
  12. //found and outDicitionary is not nil  
  13. }else{  
  14. //not found  
  15. }  
1.设置Class值,每个Class对应的都有不同的参数类型

2.用户确定的参数,一般是程序中使用的类别,比如说是"Password"或"Account Info",作为search的主力条件

3.设置Group,如果不同程序都拥有这个组,那么不同程序间可以共享这个组的数据

4.只返回第一个匹配数据,查询方法使用,还有值kSecMatchLimitAll

5.返回数据为CFDicitionaryRef,查询方法使用

6.执行查询方法,判断返回值

eg:这个是none-ARC的代码哦!ARC情况下会有bridge提示。


4.类型转换

介绍增删改方法调用前,先介绍转换方法,如何将NSDictionary转换成KeyChain方法可以设置的Dicitionary,一般在写程序过程中,应该尽量避免直接访问KeyChain,一般会创建一个NSDictionary来同步对应的数据,所以两者需要做转换。

[objc]  view plain copy print ?
  1. //data to secItem  
  2. - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert  
  3. {  
  4.     // Create a dictionary to return populated with the attributes and data.  
  5.     NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];  
  6.       
  7.     //设置kSecClass  
  8.     [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];  
  9.     //将Dictionary里的kSecValueData(一般就是这个keyChain里主要内容,比如说是password),NSString转换成NSData  
  10.     NSString *passwordString = [dictionaryToConvert objectForKey:(id)kSecValueData];  
  11.     [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];  
  12.     return returnDictionary;  
  13. }  
  14. //secItem to data  
  15. - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert  
  16. {  
  17.     NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];  
  18.       
  19.     // Add the proper search key and class attribute.  
  20.     [returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];  
  21.     [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];  
  22.       
  23.     // Acquire the password data from the attributes.  
  24.     NSData *passwordData = NULL;  
  25.     if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr)  
  26.     {  
  27.         // 删除多余的kSecReturnData数据  
  28.         [returnDictionary removeObjectForKey:(id)kSecReturnData];  
  29.           
  30.         // 对应前面的步骤,将数据从NSData转成NSString  
  31.         NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length]  
  32.                                                      encoding:NSUTF8StringEncoding] autorelease];  
  33.         [returnDictionary setObject:password forKey:(id)kSecValueData];  
  34.     }  
  35.     else  
  36.     {  
  37.         NSAssert(NO@"Serious error, no matching item found in the keychain.\n");  
  38.     }  
  39.     [passwordData release];   
  40.     return returnDictionary;  
  41. }  

5.增删改

用代码来说明

[objc]  view plain copy print ?
  1. - (void)writeToKeychain  
  2. {  
  3.     NSDictionary *attributes = NULL;  
  4.     NSMutableDictionary *updateItem = NULL;  
  5.     OSStatus result;  
  6.     //判断是增还是改  
  7.     if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)  
  8.     {  
  9.             // First we need the attributes from the Keychain.  
  10.             updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];  
  11.         // Second we need to add the appropriate search key/values.  
  12.             [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass];  
  13.             // Lastly, we need to set up the updated attribute list being careful to remove the class.  
  14.             NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];  
  15.             //删除kSecClass update不能update该字段,否则会报错  
  16.             [tempCheck removeObjectForKey:(id)kSecClass];  
  17.         //参数1表示search的,参数2表示需要更新后的值  
  18.             result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck);  
  19.     }else{  
  20.             //增加  
  21.             result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);  
  22.     }  
  23. }  

删除很简单,就不写注释了

[objc]  view plain copy print ?
  1. - (void)resetKeychainItem  
  2. {  
  3.     OSStatus junk = noErr;  
  4.     if (!keychainItemData)  
  5.     {  
  6.         self.keychainItemData = [[NSMutableDictionary alloc] init];  
  7.     }  
  8.     else if (keychainItemData)  
  9.     {  
  10.         NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];  
  11.         junk = SecItemDelete((CFDictionaryRef)tempDictionary);  
  12.         NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );  
  13.     }  
  14.       
  15.     // Default attributes for keychain item.  
  16.     [keychainItemData setObject:@"" forKey:(id)kSecAttrAccount];  
  17.     [keychainItemData setObject:@"" forKey:(id)kSecAttrLabel];  
  18.     [keychainItemData setObject:@"" forKey:(id)kSecAttrDescription];  
  19.       
  20.     // Default data for keychain item.  
  21.     [keychainItemData setObject:@"" forKey:(id)kSecValueData];  
  22. }  

二.Group的配置

配置Target的Code Signing Entitlements.


配置该文件



可以配置一个Array列表,表示该程序可以支持多个group

这样就可以在创建secItem时候添加kSecAttrAccessGroup了。

经过测试有以下经验同大家分享:

1.相同bundle下生成的程序都可以共享相同group的keyChain.

相同bundle解释下就是:比如:2个程序分别使用的provision对应bundle是com.jv.key1和com.jv.key2,那你配置文件肯定是{Identifer}.com.jv.{name},其中identifer是苹果生成的随机串号,可以在申请证书时看到,复制过来即可,name可以自己取,程序中指定属于哪个Group即可。

2.如果你在 addkey时,没有指定group,则会默认添加你keychain-access-groups里第一个group,如果你没有设置Entitlements,则默认使用对应的程序的bundle name,比如com.jv.key1,表示只能给自己程序使用。

3.如果你程序添加的group并不存在你的配置文件中,程序会奔溃,表示无法添加。因此你只能添加你配置文件中支持的keychain。



参考资料:

苹果文档:

Keychain Services Reference

Certificate, Key, and Trust Services Programming Guide

Keychain Services Programming Guide

你可能感兴趣的:(iOS keyChain 研究)