demo下载
上一篇介绍了Keychain的基本内容接下来介绍三种的使用
keychain的使用
这里总结keychain三个使用方法,分别是
- 苹果官方的KeychainItemWrapper
- 第三方封装SAMKeychain
- 通过Security.framework框架使用
这三个方法我最推崇的是使用sskeychain这个封装的方案,更加简单方便,下载和使用地址在后面,现在开始说下每一个方法的使用
KeychainItemWrapper的使用
KeychainItemWrapper是苹果官方推出的,链接地址:点击进入官方文档,这个因为是官方推出的,所以很多人用,但是会有点坑,使用方案,首先去官方地址或者后面我的demo中,把KeychainItemWrapper.h和KeychainItemWrapper.m引入工程。
使用步骤
第一步:
把KeychainItemWrapper.h和KeychainItemWrapper.m引入工程
第二步:
把KeychainItemWrapper.m使用-fno-objc-arc这个关闭arc
如图所示:
第三步:
KeychainItemWrapper *warp = [[KeychainItemWrapper alloc]initWithIdentifier:identifier accessGroup:aGroup];
[warp objectForKey:(id)kSecValueData];
//必须
[warp setObject:identifier forKey:(id)kSecAttrService];
//必须
[warp setObject:identifier forKey:(id)kSecAttrAccount];
[warp setObject:object forKey:(id)kSecValueData];
//权限
[warp setObject:(id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(id)kSecAttrAccessible];
#if TARGET_IPHONE_SIMULATOR
// Note: 如果在模拟器中运行,则无法设置访问组。模拟器中运行的应用程序没有签名,因此没有访问组供它们检查。在模拟器中运行的所有应用程序都可以看到所有的密钥链项。如果需要测试共享访问组的应用程序,则需要在设备上安装这些应用程序。
#else
if ([accessGroup length] > 0)
{
[warp setObject: accessGroup forKey:(id)kSecAttrAccessGroup];
}
#endif
优缺点
- 需禁用KeychainItemWrapper.h/.m文件的ARC,但现在流行ARC
- keychain内部应该是根据kSecAttrService和kSecAttrAccount作为标识的,所以必须设置
- 需要的参数少
SAMKeychain的使用方法
SAMKeychain是samsoffes大神封装的一个方法,不像KeychainItemWrapper需要设置太多的选项,所以很好用
github下载地址:https://github.com/soffes/SAMKeychain
使用步骤
第一步:
使用方法就是下载之后,把SSKeychain.h和SSKeychain.m文件拖入到自己的工程中,导入头文件即可
[SAMKeychain setPassword:password forService:serviceName account:account];
优缺点
- 使用比较简单
- 内部已经封好了service等内容不会崩溃
区分NSString和NSData
Security.framework的使用
这个用到了系统库#import <Security/Security.h>
是用
Keychain的用法
首先导入Security.framework 。
Keychain的API提供以下几个函数来操作Keychain
- SecItemAdd 添加一个keychain item
- SecItemUpdate 修改一个keychain item
- SecItemCopyMatching 搜索一个keychain item
- SecItemDelete 删除一个keychain item
也可以参考以下这段简单的代码来了解下Keychain API的用法。
优缺点
- 比较繁琐
安全性更高
- (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
//指定item的类型为GenericPassword
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
//类型为GenericPassword的信息必须提供以下两条属性作为unique identifier
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrService];
return searchDictionary;
}
- (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
//在搜索keychain item的时候必须提供下面的两条用于搜索的属性
//只返回搜索到的第一条item,这个是搜索条件。
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
//返回item的kSecValueData 字段。也就是我们一般用于存放的密码,返回类型为NSData *类型
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
//我来解释下这里匹配出的是 找到一条符合ksecAttrAccount、类型为普通密码类型kSecClass,返回ksecValueData字段。
NSData *result = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDictionary,
(CFTypeRef *)&result);
[searchDictionary release];
return result;
}
- (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
//非常值得注意的事kSecValueData字段只接受UTF8格式的 NSData *类型,否则addItem/updateItem就会crash,并且一定记得带上service和account字段
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:passwordData forKey:(id)kSecValueData];
OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
[dictionary release];
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[updateDictionary setObject:passwordData forKey:(id)kSecValueData];
//这里也有需要注意的地方,searchDictionary为搜索条件,updateDictionary为需要更新的字典。这两个字典中一定不能有相同的key,否则就会更新失败
OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
(CFDictionaryRef)updateDictionary);
[searchDictionary release];
[updateDictionary release];
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (void)deleteKeychainValue:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
SecItemDelete((CFDictionaryRef)searchDictionary);
[searchDictionary release];
}
使用keychain需要注意的问题
1.当我们不支持Keychain Access Group,并且没有entitlement文件时,keychain默认以bundle id为group。如果我们在版本更新的时候改变了bundle id,那么新版本就访问不了旧版本的keychain信息了。解决办法是从一开始我们就打开KeychainSharing,添加Keychain Access Group,并且指定每条keychain Item的group,私有的信息就指定app的bundle id为它的group。
2.代码内Access group名称一定要有AppIdentifierPrefix前缀。
Keychain是基于数据库存储,不允许添加重复的条目。所以每条item都必须指定对应的唯一标识符也就是那些主要的key,如果Key指定不正确,可能会出现添加后查找不到的问题。
3.kSecAttrSynchronizable也会作为主要的key之一。它的value值默认为No,如果之前添加的item此条属性为YES,在搜索,更新,删除的时候必须添加此条属性才能查找到之前添加的item。
Kechain item字典内添加自定义key时会出现参数不合法的错误。
ksecValueData必须为UTF8的NSData类型。
4.kSecClass中的主键值,不允许添加2个相同的值。否则会添加失败。 对应主要的key是一定要包含的,否则会写入失败。
5.更新函数SecItemUpdate ,第一个参数为搜索条件,第二个参数为需要更新的键值。两个不能有重复key,否则会失败。