iOS Keychain,SSKeychain,使用 理解 原理
我的邮件:m4email@163.com 如果有这篇文章对您有帮助就点下推荐或者随意评论一个呗,谢谢谢谢,随便转载,标明出处就好。
Keychain 使用? ---为了实用最大化我觉得我应该直接先说使用!
当然是使用第三方库啦:sskeychain 3000+星星的库不开玩笑。github地址:https://github.com/soffes/sskeychain
导入完之后首先,编译一下有无错。
如果是自己手动导入:
1.把SSKeychain.h SSKeychain.m SSKeychainQuery.h SSKeychainQuery.m 复制到工程
2.添加Security.framework 怎么添加?点一下那个+
3.SSKeychain.h有错?把SSKeychain.h 中的#import <SSKeychain/SSKeychainQuery.h> 换成 #import <Foundation/Foundation.h> #import "SSKeychainQuery.h" 吧。
还有错?作为小白我的也不知道了,发我邮件一起讨论吧。
接下来演示4个过程
基本说明:储存的数据有三个 1.服务名(这个方便对账号密码进行分类)2.账号3.密码 而这三个数据都是NSString (如果要存其他类型呢,请看后面吧)
所用到的API :
添加和更新都用这个: + (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account ;
查询密码:+ (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account;
删除:+ (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account;
1.添加一条钥匙 (这个钥匙的信息 由 服务名+账号+密码 组成)
记得添加头文件
#import "SSKeychain.h"
#import "SSKeychainQuery.h"
//先定义一下要用的东东
NSString *serviceName= @"com.keychaintest.data";
NSString *account = @"m4abcd";
NSString *password = @"12345678";
//加入钥匙串!
if ([SSKeychain setPassword:password forService:serviceName account:account]) {
NSLog(@"success !");
}
说明:就是这么简单咯。
2.查询
1.查询某service 下 count 的密码并且打印出来:
NSLog(@"%@",[SSKeychain passwordForService:serviceName account:account]);
2.查询service下所有钥匙:
NSArray *keys = [SSKeychain accountsForService:serviceName];
这是我的输出:
2016-03-04 15:08:43.785 keychaintest[31342:4403403] (
{
acct = m4abcd;
agrp = test;
cdat = "2016-03-03 07:10:58 +0000";
mdat = "2016-03-04 07:08:43 +0000";
pdmn = ak;
svce = "com.keychaintest.data";
sync = 0;
tomb = 0;
}
)
说明:返回的结果为数组,数组成员就是我们查询的钥匙,这里只有一个钥匙,而钥匙信息以字典的形式构建的,键acct 就是count,键svce 就是serviceName。密码在哪里?用方法1去取吧骚年!
3.查询本appkeychain的所有钥匙
NSArray *keys = [SSKeychain allAccounts];
3.更新
if([SSKeychain setPassword:@"321321" forService:serviceName account:account]){
NSLog(@"set success!");
}
4.删除
if([SSKeychain deletePasswordForService:serviceName account:account]){
NSLog(@"delete success!");
}
说明:删除就是把这一条钥匙删除哦,不是只删除密码!
另外的说明:如果你的password 是NSData
查询: + (NSData *)passwordDataForService:(NSString *)serviceName account:(NSString *)account;
设置or更新:+ (BOOL)setPasswordData:(NSData *)password forService:(NSString *)serviceName account:(NSString *)account;
下面开始浅浅的理解还有对苹果API进行一点点说明吧
1.Keychain 是什么?
keychain 就是放钥匙柜子!就是苹果提供给我们的一个保险柜。
这篇文章仅针对iOS。
在iOS中每个APP 都有属于自己的Keychain,最常用就是保存用户的账户和密码,就是记住密码,放在这里很安全(苹果负责帮我们加密再存起来,如果出了问题怪他咯!),假如用NSUserDefault 保存这些秘密数据,生成的plist文件(就放在那个Library/Preferences 下)容易被拿到,而且还要自己做加密。
特性:1.当app删除了,又再次重新安装,这个保险柜里的信息还存在哦。 所以当你的某女同学登了APP并保存了密码,你重装了APP,如果不删除记录,你女票还是可以发现的。
2.安全!作为小白的我并不知道它实际上是存在哪里的。
2.Keychain 组成?
1.组成部分由 {N个标签(属性) + 一个重要数据} 组成!
2.结构可以看成是一个字典的形式大概是这样的: @{@"属性key1":@"属性值1",@"属性keyN":@"属性值N",@"valueData":@数据}
3.内容说明:
一个重要数据:就是密码password!
N个标签:也是属性,都是用来表明这条钥匙的,如我们的serviceName ,account 都是属性,他们对应的键为 kSecAttrAccount 和 kSecAttrAccount,还有系统给我们加的创建时间,修改时间等还有label,type,port,你自己打开文件进去看看吧,这些标签的任务就是来表明这条钥匙是独一无二的。
3.原始API操作
先来看看几个API
添加钥匙: OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
查询密码与查询标签: OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
更新钥匙信息: OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
删除钥匙: OSStatus SecItemDelete(CFDictionaryRef query)
先说明一下 这些API的关键在于1.是理解和配置好这个操作字典 2.注意返回的OSStatus 状态 3.CF对象与OC 之间的bridge
1.先来一发查找
过程:
1.(关键)先配置一个操作字典内容有:
kSecAttrService(属性),kSecAttrAccount(属性) 这些属性or标签是查找的依据
kSecReturnData(值为@YES 表明返回类型为data),kSecClass(值为kSecClassGenericPassword 表示重要数据为“一般密码”类型) 这些限制条件是返回结果类型的依据
2.然后用查找的API 得到查找状态和返回数据(密码)
3.最后如果状态成功那么将数据(密码)转换成string 返回
//用原生的API 实现查询密码
- (NSString *)passwordForService:(nonnull NSString *)service account:(nonnull NSString *)account{
//生成一个查询用的 可变字典
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:4];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; //表明为一般密码可能是证书或者其他东西
[dict setObject:service forKey:(__bridge id)kSecAttrService]; //输入service
[dict setObject:account forKey:(__bridge id)kSecAttrAccount]; //输入account
[dict setObject:@YES forKey:(__bridge id)kSecReturnData]; //返回Data
//查询
OSStatus status = -1;
CFTypeRef result = NULL;
status = SecItemCopyMatching((__bridge CFDictionaryRef)dict,&result);//核心API 查找是否匹配 和返回密码!
if (status != errSecSuccess) { //判断状态
return nil;
}
//返回数据
NSString *password = [[NSString alloc] initWithData:(__bridge_transfer NSData *)result encoding:NSUTF8StringEncoding];//转换成string
return password;
}
说明:其实关键就在于这个操作字典的配置上!
2.添加&更新
说明:当添加的时候我们一般需要判断一下当前钥匙串里面是否已经存在我们要添加的钥匙。如果已经存在我们就更新好了,不存在再添加,所以这两个操作一般写成一个函数搞定吧。
过程关键:1.检查是否已经存在 构建的查询用的操作字典:kSecAttrService,kSecAttrAccount,kSecClass(标明存储的数据是什么类型,值为kSecClassGenericPassword 就代表一般的密码)
2.添加用的操作字典: kSecAttrService,kSecAttrAccount,kSecClass,kSecValueData
3.更新用的操作字典1(用于定位需要更改的钥匙):kSecAttrService,kSecAttrAccount,kSecClass
操作字典2(新信息)kSecAttrService,kSecAttrAccount,kSecClass ,kSecValueData
//用原生的API 添加一条钥匙
-(BOOL)addItemWithService:(NSString *)service account:(NSString *)account password:(NSString *)password{
//先查查是否已经存在
//构造一个操作字典用于查询
NSMutableDictionary *searchDict = [[NSMutableDictionary alloc]initWithCapacity:4];
[searchDict setObject:service forKey:(__bridge id)kSecAttrService]; //标签service
[searchDict setObject:account forKey:(__bridge id)kSecAttrAccount]; //标签account
[searchDict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];//表明存储的是一个密码
OSStatus status = -1;
CFTypeRef result =NULL;
status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDict, &result);
if (status == errSecItemNotFound) { //没有找到则添加
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; //把password 转换为 NSData
[searchDict setObject:passwordData forKey:(__bridge id)kSecValueData]; //添加密码
status = SecItemAdd((__bridge CFDictionaryRef)searchDict, NULL); //!!!!!关键的添加API
}else if (status == errSecSuccess){ //成功找到,说明钥匙已经存在则进行更新
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; //把password 转换为 NSData
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithDictionary:searchDict];
[dict setObject:passwordData forKey:(__bridge id)kSecValueData]; //添加密码
status = SecItemUpdate((__bridge CFDictionaryRef)searchDict, (__bridge CFDictionaryRef)dict);//!!!!关键的更新API
}
return (status == errSecSuccess);
}