一、简介
keychain
是一个相对独立的空间,保存到 keychain
钥匙串中的信息不会因为卸载/重装 app 而丢失, 。相对于 NSUserDefaults
、plist
文件保存等一般方式,keychain
保存更为安全。所以我们会用 keyChain
保存一些私密信息,比如密码、证书、设备唯一码(把获取到用户设备的唯一I D 存到 keychain
里面这样卸载或重装之后还可以获取到 id,保证了一个设备一个ID)等等。keychain
是用 SQLite
进行存储的。用苹果的话来说是一个专业的数据库,加密我们保存的数据,可以通过 metadata(attributes)
进行高效的搜索。keychain
适合保存一些比较小的数据量的数据,如果要保存大的数据,可以考虑文件的形式存储在磁盘上,在keychain里面保存解密这个文件的密钥。
二、使用
keychain
的使用类似于数据库,所以也有相应的增删改查操作的语句。
需要导入 Security
库引入头文件 #import <Security/Security.h>
#import "ViewController.h"
#import <Security/Security.h>
@interface ViewController ()
@end
@implementation ViewController
NSString *const accessItem = @"XXXXXXX.com.miongpao.KeyChainDemo";
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
}
- (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:(id)kSecClassGenericPassword,(id)kSecClass,service, (id)kSecAttrService,service, (id)kSecAttrAccount,(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,nil];
}
/**
增加
*/
- (void)addKeychainData:(id)data forKey:(NSString *)key{
NSMutableDictionary *keychainQuery = [self getKeychainQuery:key];
SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
[keychainQuery setObject:accessItem forKey:(id)kSecAttrAccessGroup];
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
}
/**
删除
*/
- (void)deleteWithService:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
}
/**
修改
*/
-(void)updateKeychainData:(id)data forKey:(NSString *)key {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:key];
[keychainQuery setObject:accessItem forKey:(id)kSecAttrAccessGroup];
NSData * updata = [NSKeyedArchiver archivedDataWithRootObject:data];
NSDictionary *myDate = @{(__bridge id)kSecValueData : updata};
SecItemUpdate((__bridge CFDictionaryRef)keychainQuery, (__bridge CFDictionaryRef)myDate);
}
/**
查询
*/
- (id)readForkey:(NSString *)key {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:key];
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", key, e);
} @finally {
}
}
if (keyData)CFRelease(keyData);
return ret;
}
@end
keychain 是一个结构,有很多key-value
:
1.key - kSecClass
value | 说明 |
---|---|
kSecClassGenericPassword |
一般密码 |
kSecClassInternetPassword |
网络密码 |
kSecClassCertificate |
证书 |
kSecClassKey |
密钥 |
kSecClassIdentity |
身份证书(带私钥的证书) |
2. kSecClassGenericPassword
包含的 key
key | 说明 | 类型 |
---|---|---|
kSecAttrCreationDate |
创建日期 | CFDateRef |
kSecAttrModificationDate |
最后一次修改日期 | CFDateRef |
kSecAttrDescription |
描述 | CFStringRef |
kSecAttrComment |
注释 | CFStringRef |
kSecAttrCreator |
创建者 |
CFNumberRef (4字符,如'aLXY') |
kSecAttrType |
类型 |
CFNumberRef (4字符,如'aTyp') |
kSecAttrLabel |
标签(给用户看) | CFStringRef |
kSecAttrIsInvisible |
是否隐藏 |
CFBooleanRef (kCFBooleanTrue ,kCFBooleanFalse ) |
kSecAttrIsNegative |
是否具有密码,此项表示当前的 item 是否只是一个占位项,或者说是只有 key 没有 value |
CFBooleanRef (kCFBooleanTrue ,kCFBooleanFalse ) |
kSecAttrAccount |
账户名 | CFStringRef |
kSecAttrService |
所具有服务 | CFStringRef |
kSecAttrGeneric |
用户自定义内容 | CFDataRef |
kSecAttrSecurityDomain |
网络安全域 | CFStringRef |
kSecAttrServer |
服务器域名或IP地址 | CFStringRef |
kSecAttrAccessible |
可访问性类型透明 | . |
3.key - kSecAttrAccessible
这个属性,决定了我们 item 在什么条件下可以获取到里面的内容,我们在添加 item 的时候,可以添加这个属性,来增强数据的安全性,具体的主要有以下几个:
value | 说明 |
---|---|
kSecAttrAccessibleWhenUnlocked |
解锁可访问,备份 |
kSecAttrAccessibleAfterFirstUnlock |
第一次解锁后可访问,备份 |
kSecAttrAccessibleAlways |
一直可访问,备份 |
kSecAttrAccessibleWhenUnlockedThisDeviceOnly |
解锁可访问,不备份 |
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly |
第一次解锁后可访问,不备份 |
kSecAttrAccessibleAlwaysThisDeviceOnly |
一直可访问,不备份 |
每个意思都很明确,item 默认就是 kSecAttrAccessibleWhenUnlocked
,也就是在设备未锁屏的情况下。这个也是苹果推荐的。kSecAttrAccessibleAlways
这个苹果在 WWDC 中也说了,不建议使用,苹果自己已经都弃用了。kSecAttrAccessibleAfterFirstUnlock
这个是在设备第一次解锁后,可以使用。这个最常见的就是后台唤醒功能里面,如果需要访问某个 item,那么需要使用这个属性,不然是访问不了 item 的数据的。最后几个 DeviceOnly 相关的设置,如果设置了,那么在手机备份恢复到其他设备时,是不能被恢复的。同样 iCloud 也不会同步到其他设备,因为在其他设备上是解密不出来的。
三、数据共享
同一个开发者账号下(teamID),各个应用之间可以共享 item。keychain
通过 keychain-access-groups
来进行访问权限的控制。在 Xcode 的 Capabilities
选项中打开 Keychain Sharing
即可。
//添加
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 *query1 = @{(__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 status1 = SecItemCopyMatching((__bridge CFDictionaryRef)query1, &dataTypeRef)
//只需要添加一个kSecAttrAccessGroup属性即可。
网友评论