钥匙串(keychain)
keychain服务提供了一种安全的保存私密信息(密码,序列号,私钥,证书等)的方式,每个iOS程序都有一个独立的keychain存储。相对于NSUserDefaults、文件保存等一般方式,keychain保存更为安全,而且keychain里保存的信息不会因APP被删除而丢失。
基于钥匙串的特点,我们在使用时对于新增的数据项目,要注意数据删除的时机。还有一点需要注意的就是,保护属性的指定。如果没有指定保护属性,这将被视为严重的安全漏洞。
作用
- 备份
当我们备份设备数据时,系统会将用户数据保存在相应的钥匙串中,并依照相应的安全策略保存。主要分成两类:加密和不加密。
两者的主要区别在于恢复数据时的范围不同。加密备份的数据可以恢复到任何设备上(ThisDeviceOnly指定的项目除外),而不加密的备份则只能恢复至同一台设备。
- 共享
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
如果一个文件当前被一个应用打开,那会暂时禁用该文件的保护。它会确保打开的文件在设备被锁定时依然能够写入,并且允许新建文件到磁盘。不过这个等级保护的文件在锁屏时无法打开,除非锁屏时就已经提前打开。