美文网首页iOS开发
Keychain那些事

Keychain那些事

作者: fairy_happy | 来源:发表于2018-03-05 22:38 被阅读1144次

    作为iOS开发者,我们所熟知的本地化存储方案是plist、NSUserDefaults、NSKeyedArchiver、数据库(FMDB/CoreData)。然而这几种存储方式终究逃不开沙盒系统,如果App被卸载,储存的数据也不复存在。要想规避这种情况或者想多个App共用数据,我们就要把数据存储在keychain(钥匙串)中。下面我们就聊聊Keychain那些事。

    什么是Keychain

    根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌。苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等。它是一个在所有app之外的sqlite数据库。
    Keychain内部可以保存很多的信息。每条信息作为一个单独的keychain item。keychain item一般为一个字典,每条keychain item包含一条data和很多attributes。举个例子,一个用户账户就是一条item,用户名可以作为一个attribute , 密码就是data。

    如何使用Keychain保存数据

    使用keychain一般有三种方法

    第一种 原生API(缺点:操作复杂)

    SecItemAdd 添加一个keychain item
    SecItemUpdate 修改一个keychain item
    SecItemCopyMatching 搜索一个keychain item
    SecItemDelete 删除一个keychain item

    通过如下代码了解下原生API的使用

        NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
        //指定item的类型为GenericPassword
        [searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
        
        //类型为GenericPassword的信息必须提供以下两条属性作为unique identifier
        [searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount];
        [searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrService];
        
        return searchDictionary;
    }
    - (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
        NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
        
        //在搜索keychain item的时候必须提供下面的两条用于搜索的属性
        //只返回搜索到的第一条item,这个是搜索条件。
        [searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
        //返回item的kSecValueData 字段。也就是我们一般用于存放的密码,返回类型为NSData *类型
        [searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
       
      //我来解释下这里匹配出的是 找到一条符合ksecAttrAccount、类型为普通密码类型kSecClass,返回ksecValueData字段。
        NSData *result = nil;
        OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDictionary,
                                              (CFTypeRef *)&result);
        [searchDictionary release];
        return result;
    }
    - (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
        NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
        
      //非常值得注意的事kSecValueData字段只接受UTF8格式的 NSData *类型,否则addItem/updateItem就会crash,并且一定记得带上service和account字段
        NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
        [dictionary setObject:passwordData forKey:(id)kSecValueData];
        
        OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
        [dictionary release];
        if (status == errSecSuccess) {
            return YES;
        }
        return NO;
    }
    - (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
        NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
        
        NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
        NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
        [updateDictionary setObject:passwordData forKey:(id)kSecValueData];
        
      //这里也有需要注意的地方,searchDictionary为搜索条件,updateDictionary为需要更新的字典。这两个字典中一定不能有相同的key,否则就会更新失败
        OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
                                        (CFDictionaryRef)updateDictionary);
        
        [searchDictionary release];
        [updateDictionary release];
        
        if (status == errSecSuccess) {
            return YES;
        }
        return NO;
    }
    - (void)deleteKeychainValue:(NSString *)identifier {
        NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
        SecItemDelete((CFDictionaryRef)searchDictionary);
        [searchDictionary release];
    }
    

    第二种 苹果提供的封装类 KeychainItemWrappe

    使用时可能出现一些bug,需要注意,具体使用请参阅上述链接提供的demo

    第三种 使用三方框架 SFHFKeychainUtils

    使用简单
    提供了四个方法用于增删改查
    + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
    + (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
    + (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
    + (BOOL) purgeItemsForServiceName:(NSString *) serviceName error: (NSError **) error;

    Keychain使用的注意事项

    keychain的item有5种指定类型,分别是
    extern CFTypeRef kSecClassGenericPassword
    extern CFTypeRef kSecClassInternetPassword
    extern CFTypeRef kSecClassCertificate
    extern CFTypeRef kSecClassKey
    extern CFTypeRef kSecClassIdentity OSX_AVAILABLE_STARTING(MAC_10_7, __IPHONE_2_0);
    如果把keychain item的类型指定为需要保护的类型比如password,item的data会被加密并且保护起来,如果把类型指定为不需要保护的类型,比如certificates,item的data就不会被加密。

    每种类型的Keychain item都有不同的键作为主要的Key也就是唯一标示符用于搜索,更新和删除,Keychain内部不允许添加重复的Item。

    keychain item的类型,也就是kSecClass键的值 主要的Key
    kSecClassGenericPassword kSecAttrAccount,kSecAttrService
    kSecClassInternetPassword kSecAttrAccount, kSecAttrSecurityDomain, kSecAttrServer, kSecAttrProtocol,kSecAttrAuthenticationType, kSecAttrPortkSecAttrPath
    kSecClassCertificate kSecAttrCertificateType, kSecAttrIssuerkSecAttrSerialNumber
    kSecClassKey kSecAttrApplicationLabel, kSecAttrApplicationTag, kSecAttrKeyType,kSecAttrKeySizeInBits, kSecAttrEffectiveKeySize
    kSecClassIdentity kSecClassKey,kSecClassCertificate

    keychain的备份

    iOS的Keychain由系统管理并且进行加密,Keychain内的信息会随着iPhone的数据一起备份。但是kSecAttrAccessible 属性被设置为后缀是ThisDeviceOnly的数据会被以硬件相关的密钥(key)加密。并且不会随着备份移动至其他设备。

    kSecAttrAccessiblein变量用来指定这条信息的保护程度。我们需要对这个选项特别注意,并且使用最严格的选项。这个键(key)可以设置6种值。
    CFTypeRef kSecAttrAccessibleWhenUnlocked;
    CFTypeRef kSecAttrAccessibleAfterFirstUnlock;
    CFTypeRef kSecAttrAccessibleAlways;
    CFTypeRef kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
    CFTypeRef kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
    CFTypeRef kSecAttrAccessibleAlwaysThisDeviceOnly;
    从iOS5.0开始kSecAttrAccessible默认为kSecAttrAccessibleWhenUnlocked。

    Keychain从iOS7.0开始支持iCloud备份。把kSecAttrSynchronizable属性设置为@YES,这样后Keychain就能被iCloud备份并且跨设备分享。
    不过在添加kSecAttrSynchronizable属性后,这条属性会被作为每条Keychain Item的主要的Key之一,所以在搜索,更新,删除的时候如果查询字典内没有这一条属性,item就匹配不到。

    参考

    iOS 开发keychain 使用与多个APP之间共享keychain数据的使用
    聊聊iOS Keychain

    相关文章

      网友评论

        本文标题:Keychain那些事

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