什么是Keychain:
Keychain是iOS中的一个安全的存储容器,可以为不同的应用存储敏感信心,例如,用户名,密码,网络密码,认证令牌, Wi-Fi网络密码,VPN凭证,私钥等。保存的信息存储在设备中,这个空间可能是独立于每个App沙盒之外独立的数据库。
Keychain的特点:
- 使用keychain存储可以保证程序卸载重装时存储的数据不会改变,再次安装仍可以使用之前存储的数据;
- 在相同的 Team ID 开发的App,keychain的数据可以通过group方式,让程序可以在App间共享;
- 在keychain中存储的数据是经过加密的,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
- 添加数据,添加一个item
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
attributes : 添加到Keychain的数据;
result : 这是存储数据后,返回一个指向该数据的引用,如果不是使用该引用时可以传入 NULL ;
- 查询数据,搜索一个已存在的item
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
query : 查询数据的条件;
result: 根据条件查询得到数据的引用;
- 更新数据,更新已存在的item
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
query : 要更新数据的查询条件;
attributesToUpdate : 要更新的数据;
- 删除数据,删除一个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 同步跨越多个设备;这些以后再记录!
网友评论