iOS之keychain详解(附有Demo)

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

iOS之keychain详解(附有Demo)
iOS keychain是苹果⽤来保存⽤户私密数据的⼀个专业的SQLite数据库。

保存的数据主要是⼀些轻量级的私密数据,⽐如⽤户密码,token(令牌)等,保存在这个数据库中的密码
不会因为你卸载了app就不见了,只要你重新安装app。

调⽤相关的服务、账户API就能得到app上次保存在数据库中的密码。

这对于⽤户卸载app之后⼜重新安装但是却忘记了密
码的情况很适⽤。

有⼀点很奇怪,经测试,不同的开发者账号访问同⼀服务同⼀账号下的密钥的时候竟然都能获取得到,这样岂不是会有冲突。

希望有知道原因的⼩伙伴们在评论中留⾔,解答⼀
下俺⼼中的疑问╮(~▽~)╭
iOS keychain共有5种类型: kSecClassGenericPassword、kSecClassInternetPassword、kSecClassCertificate、kSecClassKey、kSecClassIdentity
这五种类型分别有不同的属性,你可以理解为数据中5个不同的表,他们有各⾃对应的字段,因此对应的它们的增删改查其实有点类似于sql语句的操作,下⾯是不同类型对应的
属性:
现在以kSecClassGenericPassword(⼀般的密码)为例:
增加keychain
//新增keychain
- (void)addKeychainPassword{
NSDictionary *query = @{(__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleWhenUnlocked,(__bridge id)kSecClass:(__bridge id)kSecClassGenericPassword,(__bridge id)kSecValueData:[@"123456" dataUsingEncoding:NSUTF8 /*
参数⼀:
kSecAttrAccessibleWhenUnlocked 表⽰获取当前密钥只要屏幕处于解锁状态就可以了
kSecAttrAccessibleAfterFirstUnlock 表⽰⼿机第⼀次解锁就可以获取当前密钥
kSecAttrAccessibleAlways 表⽰任何时候都可以获取当前密钥
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly 表⽰获取密钥只能在当前设备,把⼿机数据恢复到新的⼿机中是不可⽤的
kSecAttrAccessibleWhenUnlockedThisDeviceOnly ⾮锁定状态,且设备唯⼀指定,同上
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 第⼀次解锁,且设备唯⼀指定,同上
kSecAttrAccessibleAlwaysThisDeviceOnly 总是可以获取,当然也是设备唯⼀指定,同上
参数⼆:
kSecClassGenericPassword 为keychain类型
参数三:
kSecValueData 存储的数据,就是密码、token存储的地⽅,要转化为NSData类型
参数四:
kSecAttrAccount 为账户名作为账户密码的唯⼀索引
参数五:
kSecAttrService 为服务名作为账户密码的唯⼀索引
*/
CFTypeRef result;
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, &result);
if (status == errSecSuccess) {
NSLog(@"添加成功");
}else{
NSLog(@"添加失败");
}
}
查询keychain:
//查询keychain
- (void)queryKeychainPassword{
NSDictionary *query = @{(__bridge id)kSecClass:(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecReturnData:@YES,
(__bridge id)kSecMatchLimit:(__bridge id)kSecMatchLimitOne,
(__bridge id)kSecAttrAccount:@"account name",
(__bridge id)kSecAttrService:@"loginPassword"};
//kSecMatchLimitOne 表⽰查询返回⼀条记录,有可能查到多条记录,⼀般默认返回⼀条记录
//kSecMatchLimitAll 表⽰返回所有记录
//SecItemCopyMatching函数会根据query⾥⾯的查询条件查找对应符合要求的记录,另外根据不同的keychain类型dataTypeRef会返回对应的不同类型如NSArray、NSDictionary、NSData
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
if (status == errSecSuccess) {
NSString *pwd = [[NSString alloc]initWithData:(__bridge NSData*)dataTypeRef encoding:NSUTF8StringEncoding];
NSLog(@"pwd:%@",pwd);
}
}
查询keychain对应的属性:
//另外可以通过kSecReturnRef查询其他属性,相对于前⾯的返回密钥的引⽤,kSecReturnRef返回的是keychain所有的属性
- (void)queryMoreAttribute{
NSDictionary *query = @{(__bridge id)kSecClass:(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecReturnRef:@YES,
(__bridge id)kSecReturnData:@YES,
(__bridge id)kSecMatchLimit:(__bridge id)kSecMatchLimitOne,
(__bridge id)kSecAttrAccount:@"account name",
(__bridge id)kSecAttrService:@"loginPassword"};
CFTypeRef dataTypeRef = NULL;
//重点是(__bridge id)kSecReturnRef:@YES,声明返回的数据是整个keychain的所有属性
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
if (status == errSecSuccess) {
NSDictionary *dict = (__bridge NSDictionary *)dataTypeRef;
NSString *acccount = dict[(id)kSecAttrAccount];
NSLog(@"acccount:%@",acccount);
NSData *data = dict[(id)kSecValueData];
NSString *pwd = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"pwd:%@",pwd);
NSString *service = dict[(id)kSecAttrService];
NSLog(@"service==result:%@", service);
}
}
更新keychain:
//修改keychain
- (void)changeKeychainPassword{
NSDictionary *query = @{(__bridge id)kSecClass:(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService:@"loginPassword",
(__bridge id)kSecAttrAccount:@"account name"};
NSDictionary *update = @{(__bridge id)kSecValueData:[@"654321" dataUsingEncoding:NSUTF8StringEncoding]};
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
if (status == errSecSuccess) {
NSLog(@"更新成功");
}
}
删除keychain:
//删除keychain
- (void)deleteKeychainPassword{
NSDictionary *query = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : @"loginPassword",
(__bridge id)kSecAttrAccount : @"account name"
};
//尽量详细的添加多个属性,避免误删其他keychain
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status == errSecSuccess) {
NSLog(@"成功删除");
}
}
iOS keychain有⼀个特⾊功能就是keychainsharing,它能实现多个同⼀个开发者账号下的多个应⽤共享keychain,前提是要开启keychainsharing功能,如下图所⽰:
可以在keychainsharing中对应的分组,把keychain添加的到分组的操作可以这样写
//sharing Items
//添加sharing Items
- (void)addSharingItems{
NSDictionary *query = @{(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked,
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecValueData : [@"88888888" dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecAttrAccount : @"account name",
(__bridge id)kSecAttrAccessGroup : @".mycom.iOS-keychain",
(__bridge id)kSecAttrService : @"loginPassword",
(__bridge id)kSecAttrSynchronizable : @YES,
};
/*
(__bridge id)kSecAttrSynchronizable : @YES 表⽰可以同步到icloud,如果要同步到其他设备,请注意避免使⽤DeviceOnly设置等其他和设备唯⼀性相关的设置
*/
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
if (status == errSecSuccess) {
NSLog(@"sharing Items添加成功");
}else{
NSLog(@"sharing Items添加失败");
}
}
查询对应的分组
//查询sharing Items
- (void)querySharingItems{
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecReturnRef : @YES,
(__bridge id)kSecReturnData : @YES,
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll,
(__bridge id)kSecAttrAccount : @"account name",
(__bridge id)kSecAttrAccessGroup : @".mycom.iOS-keychain",
(__bridge id)kSecAttrService : @"loginPassword",
};
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
if (status == errSecSuccess) {
NSLog(@"sharing Items查询成功");
}else{
NSLog(@"sharing Items查询失败");
}
}
和对单个keychain进⾏操作不同的是添加多了⼀个kSecAttrAccessGroup属性,⽤于指明对应的分组,这⾥要注意的是要添加开发者账号的teamId,⽤于区分不同的开发者。

如果都是⾃⼰定义属性进⾏增删改查的操作,是⽐较容易出bug的,⽽且操作也⽐较繁琐,毕竟这些操作都是基于C语⾔的API操作。

下⾯介绍⼀个简单的轻量级框架
SSKeychain来实现我们对密钥的增删改查,闲话少说,直接上代码:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// [SSKeychain setAccessibilityType:kSecAttrAccessibleWhenUnlocked]; //设置访问权限,不设置则按照默认权限(这个我看源码没有显⽰默认的是什么,不知道keychain默认保存的权限是不是kSecAttrAccessibleAlways) [SSKeychain setPassword:@"123456" forService:kKeyChainSaveAccountService account:kKeyChainSaveAccount]; //设置密钥
[SSKeychain setPassword:@"12345678" forService:kKeyChainSaveAccountService account:kKeyChainSaveAccount1]; //设置密钥
NSError *error;
NSString *password = [SSKeychain passwordForService:kKeyChainSaveAccountService account:kKeyChainSaveAccount error:&error]; //获取密钥
NSLog(@"password:%@,error:%@",password,error);
NSArray *counts = [SSKeychain accountsForService:kKeyChainSaveAccountService error:&error];
NSLog(@"counts:%@",counts); //获取服务下相关账户所有的属性
NSError *error1;
[SSKeychain deletePasswordForService:kKeyChainSaveAccountService account:kKeyChainSaveAccount error:&error1];
if (error1) {
NSLog(@"删除失败:%@",error1);
}else{
NSLog(@"删除成功");
}
}。

相关文档
最新文档