keychain存储详解

2019-01-12  本文已影响15人  大猿媛

Keychain是唯一一个存储安全可靠且不受应用卸载影响的存储方式

Keychain由四大项组成:
1. 条目类别kSecClass,条目类别是一个条目最基本的属性,每个存入keychain的条目,都需要为它定义一个类别,实际就是你用该条目存储什么样的数据
    条目类别的具体值有以下这些:
        kSecClassInternetPassword
        kSecClassGenericPassword
        kSecClassCertificate
        kSecClassKey
        kSecClassIdentity
    — 如:我们用该条目存储普通密码,所以设置成kSecClassGenericPassword
     [dictionary setObject:(id)kSecClassGenericPassword forKey:
     (id)kSecClass];
2. 条目id:kSecAttrGeneric
条目id就是你存储在keychain的字段值,存储条目,必须设置条目id,条目id必须是NSData,不能是NSString;
NSString *password = @"MyPasswordForFtp";
NSData *itemID= [password dataUsingEncoding:NSUTF8StringEncoding];
 [query setObject:itemID forKey:(id)kSecValueData];
3. 条目所属服务: kSecAttrService,
4. 条目所属账户名: kSecAttrAccount
   — 其中kSecAttrService和kSecAttrAccount在整个keychain里必须唯一,不能重名,就行数据库中的主键,标识了你存储的条目,所以,一般kSecAttrService就是你的bundleID
Keychain存储的的系统库和API:

— Security.framework,该库内使用的是CoreFoundation下的对象,所以在使用的时候避免不了要使用__bridge桥接方法
— keychain操作的几个API大概的中心思想是,把key-value创建的字典(其中key主要是security库中特定的几个条目key)作为主体传入API(增删改查),内部通过特定的条目kSecAttrService,kSecAttrAccount等定位查询keychain存储的数据是否有匹配项,然后再通过key-value更新keychain中存储的条目
— 重点,先设置条目类别kSecClass, [queryDic setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
— 再设置kSecAttrService,kSecAttrAccount,这样就能定位到具体条目了,
[queryDic setObject:account forKey:(id)kSecAttrAccount];
[queryDic setObject:accessGroup forKey:(id)kSecAttrAccessGroup];

1. keychain查询

OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)

1.1 使用SecItemCopyMatching进行查询,查询时,我们需要指明要查询条目的类别(kSecClass),条目的id(kSecAttrGeneric),条目所属的服务和账户(kSecAttrService,kSecAttrAccount)。此外,我们还可以设置其他一些查询条件,比如返回条目的数量(kSecMatchLimit),返回条目的数据类型,比如:
// 设置查询条件字典
NSMutableDictionary *queryDic = [NSMutableDictionary dictionary];
//设置条目类别
[queryDic setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
//设置条目所属的服务和账户,为了避免重名,我们使用常见的反转域名规则,比如com.mykeychain.password
NSString *account = @"com.mykeychain.password";
NSString *service = @"com.mykeychain.password";
[queryDic setObject:account forKey:(id)kSecAttrAccount];
[queryDic setObject:service forKey:(id)kSecAttrService];
//设置查询条件,只返回一个条目
[queryDic setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
//设置查询条件,返回条目存储的数据(kSecReturnData==True)
[queryDic setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
//开始查询
CFTypeRef result;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)dictionary , &result);
 if (queryStatus == errSecSuccess) { //查询到数据,CF和OC的桥接
        NSData *passwordData = (__bridge_transfer NSData*)result;
        NSString *password = [[NSString alloc]initWithData:passwordData encoding:NSUTF8StringEncoding];
        return password?:@"";
 }
2. keychain添加数据

OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __null

2.1 进行条目添加,我们需要指明新条目的类别(kSecClass),新条目的id(kSecAttrGeneric),新条目所属的服务和账户(kSecAttrService,kSecAttrAccount),此外,还需要指明该条目所存储的数据(kSecValueData),否则,没有数据的条目就没有任何存入keychain的意义了
// 设置条件字典
NSMutableDictionary *queryDic = [NSMutableDictionary dictionary];
//设置条目类别
[queryDic setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
//设置条目所属的服务和账户,为了避免重名,我们使用常见的反转域名规则,比如com.mykeychain.password
NSString *account = @"com.mykeychain.password";
NSString *service = @"com.mykeychain.password";
[queryDic setObject:account forKey:(id)kSecAttrAccount];
[queryDic setObject:service forKey:(id)kSecAttrService];
//设置条目id,必须是NSData类型,不能是NSString
NSString *password = @"1234567";
NSData *itemID= [password dataUsingEncoding:NSUTF8StringEncoding];
[queryDic setObject:itemID forKey:(id)kSecValueData];
//新增条目
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (status == errSecSuccess) {
     NSLog(@"——新增成功");
}
3. keychain删除条目

OSStatus SecItemDelete(CFDictionaryRef query)

3.1 删除前先查询,以上已有查询示例,通常不需要获取查询结果的时候(比如删除),只需要定义基本查询条件即可
// 设置查询条件字典
NSMutableDictionary *queryDic = [NSMutableDictionary dictionary];
//设置条目类别
[queryDic setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
//设置条目所属的服务和账户,为了避免重名,我们使用常见的反转域名规则,比如com.mykeychain.password
NSString *account = @"com.mykeychain.password";
NSString *service = @"com.mykeychain.password";
[queryDic setObject:account forKey:(id)kSecAttrAccount];
[queryDic setObject:service forKey:(id)kSecAttrService];
OSStatus queryStatus = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
if (queryStatus == errSecSuccess) { //查询到数据
     OSStatus queryStatus = SecItemDelete((__bridge CFDictionaryRef)query);
     if (queryStatus == errSecSuccess) {
        NSLog(@"%@---删除成功",account);
    }
}
4. keychain更新条目

OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)

4.1 更新前先查询,以上已有查询示例,通常不需要获取查询结果的时候(比如更新),只需要定义基本查询条件即可,查到条目后,更新条目id即可,
// 设置查询条件字典
NSMutableDictionary *queryDic = [NSMutableDictionary dictionary];
//设置条目类别
[queryDic setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
//设置条目所属的服务和账户,为了避免重名,我们使用常见的反转域名规则,比如com.mykeychain.password
NSString *account = @"com.mykeychain.password";
NSString *service = @"com.mykeychain.password";
[queryDic setObject:account forKey:(id)kSecAttrAccount];
[queryDic setObject:service forKey:(id)kSecAttrService];
OSStatus queryStatus = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
if (queryStatus == errSecSuccess) { //查询到数据
   //设置更新的条目id字典
  NSMutableDictionary *passwordDic = [NSMutableDictionary dictionary];
  NSData *itemID= [password dataUsingEncoding:NSUTF8StringEncoding];
  [passwordDic setObject:itemID forKey:(id)kSecValueData];
  OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)passwordDic);         
  if (status == errSecSuccess) {
     NSLog(@"%s——更新成功",__func__);
  }
}
OSStatus的常见code
  1. -50,可能是没有设置条目类别kSecClass,或者条目ID的数据类型不对等
    其他对应枚举值,仔细查找或自行百度
    CF_ENUM(OSStatus)
    {
    errSecSuccess = 0, /* No error. /
    errSecUnimplemented = -4, /
    Function or operation not implemented. /
    errSecDiskFull = -34, /
    The disk is full. /
    errSecDskFull = -34,
    errSecIO = -36, /
    I/O error. /
    errSecOpWr = -49, /
    File already open with write permission. /
    errSecParam = -50, /
    One or more parameters passed to a function were not valid. /
    errSecWrPerm = -61, /
    Write permissions error. /
    errSecAllocate = -108, /
    Failed to allocate memory. /
    errSecUserCanceled = -128, /
    User canceled the operation. /
    errSecBadReq = -909, /
    Bad parameter or invalid state for operation. */
ARC中的CF和OC类型相互转换
1. 在使用查询方法并获取结果的时候,需要传入CFTypeRef 类型的地址参数

SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)

1.1 在MRC下,我们可以直接使用NSData *result = nil;

SecItemCopyMatching(CFDictionaryRef query, (CFTypeRef *)&result), 但是ARC下不允许如此使用,所以就得老实传入CFTypeRef类型指针

1.2 CFTypeRef result;SecItemCopyMatching(CFDictionaryRef query, &result),得到的结果又必须转为NSData类型,这个时候就需要CF和OC类型的转换了
1.3 ARC模式下CF类型与OC类型之间的转换有所了解,最常用的有两个转换关键字,
(__bridge type)expression 和 (__bridge_transfer Objective-C type)expression。(__bridge type)expression //type 为id 或者 void* , expression为带有CF前缀类型的变量或者 void* 变量。

如下所示
1、id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
2、CGImageRef cgimage
self.layer.contents = (__bridge id)cgimage;
(__bridge_transfer Objective-C type)expression //type为OC类型 expression 可以是带有CF前缀的类型变量 。
3、
CFTypeRef result;
NSData passwordData = (__bridge_transfer NSData)result;
搞定了

上一篇下一篇

猜你喜欢

热点阅读