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

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

相关推荐
HarderCoder2 小时前
iOS 知识积累第一弹:从 struct 到 APP 生命周期的全景复盘
ios
叽哥13 小时前
Flutter Riverpod上手指南
android·flutter·ios
用户092 天前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan2 天前
iOS26适配指南之UIColor
ios·swift
权咚2 天前
阿权的开发经验小集
git·ios·xcode
用户092 天前
TipKit与CloudKit同步完全指南
ios·swift
法的空间2 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918413 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
00后程序员张3 天前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
Magnetic_h3 天前
【iOS】设计模式复习
笔记·学习·ios·设计模式·objective-c·cocoa