一、Keychain 基础
根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌。苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等。它是一个sqlite数据库,位于/private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的。
NSUserDefaults存储数据信息,但是对于一些私密信息,比如密码、证书等等,就需要使用更为安全的keychain了。keychain里保存的信息不会因App被删除而丢失,在用户重新安装App后依然有效,数据还在。
使用苹果官方发布的KeychainItemWrapper或者SFHFKeychainUtils很方便,后来看到 iphone使用keychain来存取用户名和密码 一文,觉得对了解keychain有很大的帮助,
开发者通常会希望能够利用操作系统提供的功能来保存凭证(credentials)而不是把它们(凭证)保存到NSUserDefaults,plist文件等地方。保存这些数据的原因是开发者不想用户每次都要登录,因此会把认证信息保存到设备上的某个地方并且在用户再次打开应用的时候用这些数据自动登录。Keychain的信息是存在于每个应用(app)的沙盒之外的。通过keychain access groups可以在应用之间共享keychain中的数据。要求在保存数据到keychain的时候指定group。
每一个keyChain的组成如图,整体是一个字典结构.
1.kSecClass key 定义属于那一种类型的keyChain
2.不同的类型包含不同的Attributes,这些attributes定义了这个item的具体信息
3.每个item可以包含一个密码项来存储对应的密码
使用:
引入Security包,引入文件 #import <Security/Security.h>
添加
- (IBAction)add:(id)sender {
if (nameField.text.length > 0 && passwordField.text.length > 0) {
// 一个mutable字典结构存储item信息
NSMutableDictionary* dic = [NSMutableDictionary dictionary];
// 确定所属的类class
[dic setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
查找
// 查找全部
- (IBAction)sel:(id)sender {
NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass,
kSecMatchLimitAll,kSecMatchLimit,
kCFBooleanTrue,kSecReturnAttributes,nil];
CFTypeRef result = nil;
OSStatus s = SecItemCopyMatching((CFDictionaryRef)query, &result);
NSLog(@"se;ect all : %ld",s);
NSLog(@"%@",result);
}
// 按名称查找
- (IBAction)sname:(id)sender {
if (nameField.text.length >0) {
// 查找条件:1.class 2.attributes 3.search option
NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass,
nameField.text,kSecAttrAccount,
kCFBooleanTrue,kSecReturnAttributes,nil];
CFTypeRef result = nil;
// 先找到一个item
OSStatus s = SecItemCopyMatching((CFDictionaryRef)query, &result);
NSLog(@"select name : %ld",s); // errSecItemNotFound 就是找不到
NSLog(@"%@",result);
if (s == noErr) {
// 继续查找item的secValue
NSMutableDictionary* dic = [NSMutableDictionary dictionaryWithDictionary:result];
// 存储格式
[dic setObject:(id)kCFBooleanTrue forKey:kSecReturnData];
// 确定class
[dic setObject:[query objectForKey:kSecClass] forKey:kSecClass];
NSData* data = nil;
// 查找secValue
if (SecItemCopyMatching((CFDictionaryRef)dic, (CFTypeRef*)&data) == noErr) {
if (data.length)
NSLog(@"%@",[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
}
}
}
}
修改
- (IBAction)update:(id)sender {
if (nameField.text.length >0 && passwordField.text.length > 0) {
// 先查找看看有没有
NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass,
nameField.text,kSecAttrAccount,
kCFBooleanTrue,kSecReturnAttributes,nil];
CFTypeRef result = nil;
if (SecItemCopyMatching((CFDictionaryRef)query, &result) == noErr)
{
// 更新后的数据,基础是搜到的result
NSMutableDictionary* update = [NSMutableDictionary dictionaryWithDictionary:(NSDictionary*)result];
// 修改要跟新的项 注意先加后删的class项
[update setObject:[query objectForKey:kSecClass] forKey:kSecClass];
[update setObject:[passwordField.text dataUsingEncoding:NSUTF8StringEncoding] forKey:kSecValueData];
[update removeObjectForKey:kSecClass];
#if TARGET_IPHONE_SIMULATOR
// 模拟器的都有个默认的组“test”,删了,不然会出错
[update removeObjectForKey:(id)kSecAttrAccessGroup];
#endif
// 得到要修改的item,根据result,但要添加class
NSMutableDictionary* updateItem = [NSMutableDictionary dictionaryWithDictionary:result];
[updateItem setObject:[query objectForKey:(id)kSecClass] forKey:(id)kSecClass];
// SecItemUpdate
OSStatus status = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)update);
NSLog(@"update:%ld",status);
删除
- (IBAction)del:(id)sender {
if (nameField.text.length >0) {
// 删除的条件
NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass,
nameField.text,kSecAttrAccount,nil];
// SecItemDelete
OSStatus status = SecItemDelete((CFDictionaryRef)query);
NSLog(@"delete:%ld",status); // // errSecItemNotFound 就是没有
}
}
注意:
1.区别(标识)一个item要用kSecAttrAccount和kSecAttrService
// 设置其他属性attributes [dic setObject:nameField.text forKey:(id)kSecAttrAccount]; // 添加密码 secValue 注意是object 是 NSData [dic setObject:[passwordField.text dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData]; // SecItemAdd OSStatus s = SecItemAdd((CFDictionaryRef)dic, NULL); NSLog(@"add : %ld",s); }}