美文网首页
keychain存储详解

keychain存储详解

作者: 大猿媛 | 来源:发表于2019-01-12 17:14 被阅读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),返回条目的数据类型,比如:
    • kSecReturnData:返回条目所存储的数据,返回值类型是CFDataRef
    • kSecReturnAttributes:返回该条目的属性,返回值是字典类型CFDictionaryRef
    • kSecReturnRef:返回条目的引用,根据条目所属类别,返回值类型可能是:SecKeychainItemRef, SecKeyRef,SecCertificateRef, SecIdentityRef.
    • kSecReturnPersistentRef:返回条目的引用,返回值类型是CFDataRef
    // 设置查询条件字典
    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;
    搞定了

    相关文章

      网友评论

          本文标题:keychain存储详解

          本文链接:https://www.haomeiwen.com/subject/ygkvrqtx.html