美文网首页
iOS Keychain的使用总结记录

iOS Keychain的使用总结记录

作者: 喔牛慢慢爬 | 来源:发表于2019-09-29 11:01 被阅读0次
什么是Keychain:

Keychain是iOS中的一个安全的存储容器,可以为不同的应用存储敏感信心,例如,用户名,密码,网络密码,认证令牌, Wi-Fi网络密码,VPN凭证,私钥等。保存的信息存储在设备中,这个空间可能是独立于每个App沙盒之外独立的数据库。

Keychain的特点:
  • 使用keychain存储可以保证程序卸载重装时存储的数据不会改变,再次安装仍可以使用之前存储的数据;
  • 在相同的 Team ID 开发的App,keychain的数据可以通过group方式,让程序可以在App间共享;
  • 在keychain中存储的数据是经过加密的,Keychain 的安全机制保证了存储这些敏感信息不会被窃取;

\color{red}{注:但当刷机或者升级系统后keychain中存储的数据仍会被清空,}
\color{red}{在越狱的设备上,可以通过一些相应的工具很轻松的 获取 所有的 Keychain 数据}

Keychain的结构

Keychain的内部存储了多个keychain item,每条keychain item一般为字典结构,每条keychain item包含相应的数据和一系列相关属性描述,每个应用只能打开自己对应的keychain item。

Keychain的使用

由于项目中要求使用keychain保存设备的UUID,所以使用以UUID的保存为例子。

  • 头文件导入:
#import <Security/Security.h>
  • kSecClass设置keychain存储类型:
extern const CFStringRef kSecClassInternetPassword  //网络密码类型
extern const CFStringRef kSecClassGenericPassword //普通密码类型
extern const CFStringRef kSecClassCertificate //证书类型
extern const CFStringRef kSecClassKey //关键项
extern const CFStringRef kSecClassIdentity //身份标示
  • kSecAttrAccessible设置Keychain的备份:

从iOS5.0开始kSecAttrAccessible默认为kSecAttrAccessibleWhenUnlocked
kSecAttrAccessible 属性被设置为后缀是ThisDeviceOnly的数据会被以硬件相关的密钥(key)加密。并且不会随着备份移动至其他设备

kSecAttrAccessibleWhenUnlocked 
kSecAttrAccessibleAfterFirstUnlock
kSecAttrAccessibleAlways
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
kSecAttrAccessibleAlwaysThisDeviceOnly
  • 关键API
  1. 添加数据,添加一个item
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result)

attributes : 添加到Keychain的数据;
result : 这是存储数据后,返回一个指向该数据的引用,如果不是使用该引用时可以传入 NULL ;

  1. 查询数据,搜索一个已存在的item
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)

query : 查询数据的条件;
result: 根据条件查询得到数据的引用;

  1. 更新数据,更新已存在的item
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)

query : 要更新数据的查询条件;
attributesToUpdate : 要更新的数据;

  1. 删除数据,删除一个item
OSStatus SecItemDelete(CFDictionaryRef query)

query : 删除的数据的查询条件;

创建Keychain的管理类
  • 使用Keychain的管理类
NSString *uuidStr = [KeyChainManager keyChainReadData:kUUID];
BOOL saveStatu = [KeyChainManager keyChainSaveData:uuidStr withIdentifier:kUUID];
if (saveStatu) {
      DDLogInfo(@"****** 设备UUID保存成功 ******");
}
  • KeyChainManager.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface KeyChainManager : NSObject

+ (instancetype)sharedManager;

/*
 保存数据
 
 @data  要存储的数据
 @identifier 存储数据的标示
 */
+(BOOL)keyChainSaveData:(id)data withIdentifier:(NSString*)identifier;

/*
 读取数据
 
 @identifier 存储数据的标示
 */
+(id)keyChainReadData:(NSString*)identifier;

/*
 更新数据
 
 @data  要更新的数据
 @identifier 数据存储时的标示
 */
+(BOOL)keyChainUpdata:(id)data withIdentifier:(NSString*)identifier;

/*
 删除数据
 
 @identifier 数据存储时的标示
 */
+(void)keyChainDelete:(NSString*)identifier;


@end

NS_ASSUME_NONNULL_END
  • KeyChainCore.m
#import "KeyChainCore.h"
#import <Security/Security.h>

@implementation KeyChainCore

+ (instancetype)sharedManager {
    static dispatch_once_t onceToken;
    static KeyChainCore *manager;
    dispatch_once(&onceToken, ^{
        manager = [[self alloc] initKeyChainManager];
    });
    return manager;
}

- (instancetype)init {
    NSAssert(NO, @"Singleton class, use shared method");
    return nil;
}

- (instancetype)initKeyChainManager {
    self = [super init];
    if (!self) return nil;
    return self;
}


+(NSMutableDictionary*)keyChainIdentifier:(NSString*)identifier
{
    NSMutableDictionary * keyChainInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                                       (id)kSecClassGenericPassword,kSecClass,
                                                       identifier,kSecAttrService,
                                                       identifier,kSecAttrAccount,
                                                       (id)kSecAttrAccessibleAfterFirstUnlock,kSecAttrAccessible, nil];
    return keyChainInfo;
}


/*
 保存数据
 
 @data  要存储的数据
 @identifier 存储数据的唯一标示
 */
+(BOOL)saveDataForKeyChain:(id)data identifier:(NSString*)identifier
{
    //获取存储数据的必要条件
    NSMutableDictionary *saveQueryInfo = [self keyChainIdentifier:identifier];
    //删除旧的数据
    SecItemDelete((CFDictionaryRef)saveQueryInfo);
    //设置需要存储的新的数据
    [saveQueryInfo setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    //添加数据,saveState返回添加数据是否成功状态
    OSStatus saveState = SecItemAdd((CFDictionaryRef)saveQueryInfo, nil);
    if (saveState == errSecSuccess) {
        return YES;
    }
    return NO;
}

/*
 读取数据
 
 @identifier 存储数据的标示
 */
+(id)readDataFromKeyChain:(NSString*)identifier
{
    id objc = nil ;
    //通过标记获取数据查询条件
    NSMutableDictionary *readQueryInfo = [self keyChainIdentifier:identifier];
    // 设置搜索条件:这是获取数据的时,必须提供的两个属性
    // 查询结果返回类型为: kSecValueData
    [readQueryInfo setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    // 只返回搜索到的第一条数据
    [readQueryInfo setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    // 创建一个数据对象
    CFDataRef keyChainData = nil ;
    // 通过条件查询数据
    if (SecItemCopyMatching((CFDictionaryRef)readQueryInfo , (CFTypeRef *)&keyChainData) == noErr){
        @try {
            objc = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)(keyChainData)];
        } @catch (NSException * exception){
            NSLog(@"Unarchive of search data where %@ failed of %@ ",identifier,exception);
        }
    }
    if (keyChainData) {
        CFRelease(keyChainData);
    }
    return objc;
}


/*
 更新数据
 
 @data  要更新的数据
 @identifier 数据存储时的标示
 */
+(BOOL)updataForKeyChain:(id)data identifier:(NSString*)identifier
{
    // 通过标记获取数据更新的必要条件
    NSMutableDictionary * updataQueryInfo = [self keyChainIdentifier:identifier];
    // 创建更新数据字典
    NSMutableDictionary * updataInfo = [NSMutableDictionary dictionaryWithCapacity:0];
    // 存储数据
    [updataInfo setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    // 获取更新存储的状态
    OSStatus  updataStatus = SecItemUpdate((CFDictionaryRef)updataQueryInfo, (CFDictionaryRef)updataInfo);
    if (updataStatus == errSecSuccess) {
        return  YES ;
    }
    return NO;
}

/*
 删除数据
 
 @identifier 数据存储时的标示
 */
+(void)deleteFromKeyChain:(NSString*)identifier
{
    // 获取删除数据的查询条件
    NSMutableDictionary * deleteQueryInfo = [self keyChainIdentifier:identifier];
    // 删除指定条件的数据
    SecItemDelete((CFDictionaryRef)deleteQueryInfo);
}
@end

还可以实现在同一个开发者账号下开发的App,并且同时选择了相同的 Team ID的应用间共享 Keychain 数据,keychain默认以bundle id为group;iOS 7 之后,Keychain 数据还可以通过 iCloud 同步跨越多个设备;这些以后再记录!

相关文章

网友评论

      本文标题:iOS Keychain的使用总结记录

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