iOS keychain

钥匙串(keychain)

keychain服务提供了一种安全的保存私密信息(密码,序列号,私钥,证书等)的方式,每个iOS程序都有一个独立的keychain存储。相对于NSUserDefaults、文件保存等一般方式,keychain保存更为安全,而且keychain里保存的信息不会因APP被删除而丢失。

基于钥匙串的特点,我们在使用时对于新增的数据项目,要注意数据删除的时机。还有一点需要注意的就是,保护属性的指定。如果没有指定保护属性,这将被视为严重的安全漏洞。

作用

  1. 备份

当我们备份设备数据时,系统会将用户数据保存在相应的钥匙串中,并依照相应的安全策略保存。主要分成两类:加密和不加密。

两者的主要区别在于恢复数据时的范围不同。加密备份的数据可以恢复到任何设备上(ThisDeviceOnly指定的项目除外),而不加密的备份则只能恢复至同一台设备。

  1. 共享

keychain支持在多个应用之间共享数据。但现实开发中UIPasteboardNameFind的使用代替了keychain。不过好在Apple在iOS11中已经废弃它。看来Apple想规范开发者对于用户敏感数据的共享使用,同时提醒大家使用更加安全的方式存储用户敏感数据。

使用范例

常用方法:

方法 描述
SecItemAdd 添加数据
SecItemDelete 删除数据
SecItemUpdate 修改数据
SecItemCopyMatching 查找数据

SecItemAdd

ini 复制代码
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSData *passwordData = [@"myPassword" dataUsingEncoding:NSUTF8StringEncoding];

[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[dict setObject:@"Conglomco" forKey:(__bridge id)kSecAttrLabel];
[dict setObject:@"This is your password for the Conglomco service." forKey:(__bridge id)kSecAttrDescription];
[dict setObject:@"chars" forKey:(__bridge id)kSecAttrAccount];
[dict setObject:@"cn.zaker.keychain.sample" forKey:(__bridge id)kSecAttrService];
[dict setObject:passwordData forKey:(__bridge id)kSecValueData];
[dict setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];

OSStatus error = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);
if (error == errSecSuccess) {
NSLog(@"Yay");
}

SecItemDelete

ini 复制代码
NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[dict setObject:@"chars" forKey:(__bridge id)kSecAttrAccount];
[dict setObject:@"cn.zaker.keychain.sample" forKey:(__bridge id)kSecAttrService];

OSStatus error = SecItemDelete((__bridge CFDictionaryRef)dict);
if (error == errSecSuccess) {
NSLog(@"Yay");
}

SecItemUpdate

ini 复制代码
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSData *newPasswordData = [@"newMyPassword" dataUsingEncoding:NSUTF8StringEncoding];

[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[dict setObject:@"chars" forKey:(__bridge id)kSecAttrAccount];
[dict setObject:@"cn.zaker.keychain.sample" forKey:(__bridge id)kSecAttrService];

NSDictionary *updatedAttribute = [NSDictionary dictionaryWithObject:newPasswordData forKey:(__bridge id)kSecValueData];

OSStatus error = SecItemUpdate((__bridge CFDictionaryRef)dict, (__bridge CFDictionaryRef)updatedAttribute);
if (error == errSecSuccess) {
NSLog(@"Yay");
}

SecItemCopyMatching

ini 复制代码
NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[dict setObject:@"chars" forKey:(__bridge id)kSecAttrAccount];
[dict setObject:@"cn.zaker.keychain.sample" forKey:(__bridge id)kSecAttrService];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];

NSDictionary *result = nil;
OSStatus error = SecItemCopyMatching((__bridge CFDictionaryRef)dict, (void *)&result);
if (error == errSecSuccess) {
NSLog(@"Yay %@", result);
}

保护属性

密码属性
项目类 描述
kSecClassGenericPassword 普通密码
kSecClassInternetPassword 专门用于互联网服务的密码
kSecClassCertificate 加密证书
kSecClassKey 加密密钥
kSecClassIdentity 一个密钥对(包括公共证书和私钥)
钥匙串保护属性:
钥匙串保护属性 含义
kSecAttrAccessibleAfterFirstUnlock 开机之后密钥不可用,直到用户首次输入开机密码
kSecAttrAccessibleAlways 密钥在设备开机后依旧可用.在iOS9中已经废弃
kSecAttrAccessibleAlwaysThisDeviceOnly 密钥始终可用,但无法迁移到其他设备
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 作用同上
kSecAttrAccessibleWhenUnlocked 只要解锁过设备,则密钥保持可用状态
kSecAttrAccessibleWhenUnlockedThisDeviceOnly 作用同上
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly 作用同上,只有用户设置密码密钥才可用
kSecAttrAccessibleAlways 将会引入一个很明显的安全问题,因为此种保护属性,只要有人窃取了你的设备,他们就能读取钥匙串的内容。
  • kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 这个属性可以完美的解决上述的安全问题。因为越狱时通常需要重启设备。

  • kSecAttrAccessibleWhenUnlocked 这个属性要求攻击者必须知道用户密码才能提取隐私数据。它很适合做默认属性值。

  • kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly 是iOS8新增的保护属性。这个属性要求在使用时用户设定了密码,否则使用就失败。

iCloud 同步

这是iOS7引入的一种新机制,可以把钥匙串项目同步到iCloud,允许用户在多个设备之间共享钥匙串项目。

默认情况下,应用程序创建的钥匙串项目会禁用这个机制,但可以把kSecAttrSynchronizable设置为true来启用。

另外请注意,使用此选项时无法指定不兼容的kSecAttrAccessible属性。例如,指定kSecAttrAccessibleWhenUnlockedThisDeviceOnly不起作用,因为ThisDeviceOnly指定的项目不会备份,也不能同步到iCloud、笔记本电脑、台式机或其他同步位置。

数据保护

Apple 推出了数据保护API作为额外的保护层,它允许开发者指定文件解密密钥的生命周期。可以使用这个API控制文件的访问权限,与钥匙串项目中的kSecAttrAccessible 属性类似。

数据保护API使用用户密码和层级密钥来加密保护文件的密钥,而当这些文件不能被访问时,会从内存中删除这个层级密钥。

文件保护过程:

1.文件生成一个文件密钥来加密文件的内容。

2.生成一个额外的密钥对,用于生成文件公钥和文件私钥。

3.用文件私钥和Protected Unless Open等级公钥计算出一个共享密码。

4.用共享密码 SHA-1散列值加密文件密钥。

5.加密过的文件密钥会存储在文件的元数据中,元数据中还有文件的公钥。

6.系统丢弃文件私钥。

7.关闭文件时从内存中删除未加密的文件密钥。

8.需要再次打开文件时,用Protected Unless Open等级私钥和文件公钥计算共享密码。

9.计算共享密码的SHA-1散列值,把它当作解密文件的密钥。

DataProtectionClass 权限

如果你的应用在设备进入后台或锁定时不需要写入或读取文件,那你就可以在工程中配置一个NSFileProtectionComplete值来添加权限。这将确保所有受保护的文件数据只能在设备解锁时访问,相当于为所有的应用文件都设置kSecAttrAccessibleWhenUnlocked选项。

从Xcode5开始,新工程会默认启用数据保护权限,但是一些旧工程并不会自动开启。

使用范例

NSDataWritingFileProtectionComplete

ini 复制代码
NSData *data = [self generateData];
NSError *error = nil;
NSString *path = [NSString stringWithFormat:@"%@_demo.pdf", NSTemporaryDirectory()];
[data writeToFile:path options:NSDataWritingFileProtectionComplete error:&error];
if (error) {
NSLog(@"%@", error);
}

NSFileProtectionComplete

objectivec 复制代码
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"demo.txt"];
NSError *error = nil;
NSDictionary *attr = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
[[NSFileManager defaultManager] setAttributes:attr ofItemAtPath:path error:&error];
if (error) {
NSLog(@"%@", error);
}

SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN

ini 复制代码
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"demo.sqlite"];
sqlite3 *handle = NULL;
sqlite3_open_v2([path UTF8String],
&handle,
SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN,
NULL);

保护等级

CompleteUntilFirstUserAuthentication

这个保护等级是iOS5开始使用的默认值。主要用来防御一些需要重启的攻击。

Complete

官方称这是目前最安全的文件保护等级。在这种情况下,锁屏之后系统就会删除内存中的层级密钥,并把文件改为不可读。

具体使用的范例前文可见。

使用Complete保护之前,考虑是否合适。如果你的应用进程需要持续写/读一个文件,那这种保护模式就不合适了。

CompleteUnlessOpen

如果一个文件当前被一个应用打开,那会暂时禁用该文件的保护。它会确保打开的文件在设备被锁定时依然能够写入,并且允许新建文件到磁盘。不过这个等级保护的文件在锁屏时无法打开,除非锁屏时就已经提前打开。

相关推荐
ii_best2 小时前
ios按键精灵自动化的脚本教程:自动点赞功能的实现
运维·ios·自动化
app开发工程师V帅13 小时前
iOS 苹果开发者账号: 查看和添加设备UUID 及设备数量
ios
CodeCreator181813 小时前
iOS AccentColor 和 Color Set
ios
m0_7482389217 小时前
webgis入门实战案例——智慧校园
开发语言·ios·swift
Legendary_00821 小时前
LDR6020在iPad一体式键盘的创新应用
ios·计算机外设·ipad
/**书香门第*/1 天前
Laya ios接入goole广告,搭建环境 1
ios
wakangda2 天前
React Native 集成 iOS 原生功能
react native·ios·cocoa
crasowas2 天前
iOS - 超好用的隐私清单修复脚本(持续更新)
ios·app store
ii_best2 天前
ios按键精灵脚本开发:ios悬浮窗命令
ios