keychain(一)

作者: JamesYu | 来源:发表于2016-06-14 01:07 被阅读1723次

    iOS keychain 主要是用来保存一些用户敏感数据。比如用户密码,token。keychain是用SQLite进行存储的。用苹果的话来说是一个专业的数据库,加密我们保存的数据,可以通过metadata(attributes)进行高效的搜索。keychain适合保存一些比较小的数据量的数据,如果要保存大的数据,可以考虑文件的形式存储在磁盘上,在keychain里面保存解密这个文件的密钥。

    keychain的类型

    • kSecClassGenericPassword
    • kSecClassInternetPassword
    • kSecClassCertificate
    • kSecClassKey
    • kSecClassIdentity

    这5个类型只是对应于不同的item,存储的属性有区别,使用上都是一样的。

    不同类型对应的属性:

    14658371868385.jpg14658371868385.jpg

    既然苹果是采用SQLite去存储的,那么以上这些不同item的attribute可以理解是数据库里面表的字段。那么对keychain的操作其实也就是普通数据库的增删改查了。这样也许就会觉得那些API也没那么难用了。

    
    NSDictionary *query = @{(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked,
                                (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
                                (__bridge id)kSecValueData : [@"1234562" dataUsingEncoding:NSUTF8StringEncoding],
                                (__bridge id)kSecAttrAccount : @"account name",
                                (__bridge id)kSecAttrService : @"loginPassword",
                                };
       
        CFErrorRef error = NULL;
       
        OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
    
    

    以这个添加kSecClassGenericPassword item为例,在字典里面我们设置了以下几个属性:获取权限为当设备处于未锁屏状态,item的类型为kSecClassGenericPassword,item的value为@"123456", item的账户名为@"account name", item的service为@"loginPassword"。最后,调用SecItemAdd进行插入。使用上有点像CoreData。

    
    NSDictionary *query = @{
                                (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
                                (__bridge id)kSecAttrService : @"loginPassword",
                                (__bridge id)kSecAttrAccount : @"account name"
                                };
        
        OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
    
    

    删除同样也是指定之前存的item的属性,最后调用SecItemDelete这个方法。这边要注意的是劲量用多个字段确定这个item,(虽然平常开发都可能是唯一)防止删除了其他item;比如我们把kSecAttrAccount这个属性去掉,那么将会删除所有的kSecAttrService对应value为@"loginPassword"的item;

    
    NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
                                (__bridge id)kSecAttrAccount : @"account name",
                                (__bridge id)kSecAttrService : @"loginPassword",
                                };
        NSDictionary *update = @{
                                 (__bridge id)kSecValueData : [@"654321" dataUsingEncoding:NSUTF8StringEncoding],
                                 };
        
        OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
    
    

    苹果推荐我们用SecItemUpdate去修改一个已经存在的item,可能我们喜欢先调用SecItemDelete方法去删除,再添加一个新的。这个主要目的是防止新添的item丢失了原来的部分属性。这个方法需要两个入参,一个字典是用来指定要更新的item,另一个字典是想要更新的某个属性的value,最后调用SecItemUpdate。

    
    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",
                                };
        
        CFTypeRef dataTypeRef = NULL;
        
        OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
        
        if (status == errSecSuccess) {
            NSString *pwd = [[NSString alloc] initWithData:(__bridge NSData * _Nonnull)(dataTypeRef) encoding:NSUTF8StringEncoding];
            NSLog(@"==result:%@", pwd);
        }
    
    

    查和前面几个操作类似,首先同样是指定属性定位到这个item,最后调用SecItemCopyMatching方法。既然是数据库查询,肯定会有记录的条数的问题。本例中使用了kSecMatchLimitOne,表示返回结果集的第一个,当然这个也是默认的。如果是查询出多个,kSecMatchLimitAll可以使用这个,那么返回的将是个数组。SecItemCopyMatching方法的入参dataTypeRef,是一个返回结果的引用,会根据不同的item,返回对应不同的类型(如NSCFData, NSCFDictionary, NSCFArray等等)。

    刚刚上面是返回存储的value的引用,如果我们想看看这个item所有的属性怎么办?我们可以使用kSecReturnRef

    
     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 : @"noraml",
                                };
        
        CFTypeRef dataTypeRef = NULL;
        
        OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
        
        if (status == errSecSuccess) {
            NSDictionary *dict = (__bridge NSDictionary *)dataTypeRef;
            NSString *acccount = dict[(id)kSecAttrAccount];
            NSData *data = dict[(id)kSecValueData];
            NSString *pwd = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSString *service = dict[(id)kSecAttrService];
            NSLog(@"==result:%@", dict);
        }
    
    
    

    这样,我们就得到了这个item的所有属性。

    Sharing Items

    通一个开发者账号下,各个应用之间可以共享item。keychain通过keychain-access-groups
    来进行访问权限的控制。在Xcode的Capabilities选项中打开Keychain Sharing即可。

    每个group命名开头必须是开发者账号的teamId。不同开发者账号的teamId是唯一的,所以苹果限制了只有同一个开发者账号下的应用才可以进行共享。如果有多个sharedGroup,在添加的时候如果不指定,默认是第一个group。

    添加:

    
    NSDictionary *query = @{(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked,
                                (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
                                (__bridge id)kSecValueData : [@"1234562" dataUsingEncoding:NSUTF8StringEncoding],
                                (__bridge id)kSecAttrAccount : @"account name",
                                (__bridge id)kSecAttrAccessGroup : @"XEGH3759AB.com.developer.test",
                                (__bridge id)kSecAttrService : @"noraml1",
                                (__bridge id)kSecAttrSynchronizable : @YES,
                                };
        
        CFErrorRef error = NULL;
        
        OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
    
    

    取:

    
    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 : @"XEGH3759AB.com.developer.test",
                                (__bridge id)kSecAttrService : @"noraml1",
                                };
        
        CFTypeRef dataTypeRef = NULL;
        
        OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
    
    

    只需要添加一个kSecAttrAccessGroup属性即可。

    相关文章

      网友评论

      • 阙笙:帅哥,本人用的Mac 迷你的系统,在library-》keychains的文件家里,找不到login.keychains这个文件。

        现在我需要这个login.keychain的文件。

        请问怎么办???
      • b2c7a522e27e:感觉楼主的分享,这是我看到写keychian最简洁易懂的文章,赞!
      • 老司机Wicky:mark以后看

      本文标题:keychain(一)

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