4、本地数据安全:NSUserDefaults安全、CoreData加密、SQLite数据库加密、文件保护等级
本地数据安全,说白了就是你的App在用户手机里存的东西,到底安不安全。
我见过太多开发者,把用户密码、Token直接往NSUserDefaults里一塞,就觉得万事大吉了。嗯,这其实跟把家门钥匙放在门口脚垫下面差不多——防君子不防小人。
今天咱们就聊聊,iOS本地存储的四个核心环节,怎么才能真正做到「安全」。
4.1 NSUserDefaults:别让它成为你的「数据裸奔」入口
NSUserDefaults,苹果官方说它是「轻量级持久化存储」。我个人习惯叫它「明文小仓库」——因为它默认就是明文存储的。
你想想看,一个plist文件,放在App的Library/Preferences目录下,用iTools或者任何文件查看器,连越狱都不用,就能直接打开看。
那怎么办?我的做法是:
- 只存非敏感配置:比如用户偏好设置、界面状态、是否第一次启动。
- 敏感数据必须加密后再存:用AES加密后,把密文存进去。
- 开启NSUserDefaults的Keychain备份?别想了,它不支持。
我曾经在一个金融类App的审计中,发现开发者把用户的身份证号直接存到了NSUserDefaults里。我问为什么,他说「方便读取」。嗯,方便了黑客也方便了你。
4.2 CoreData加密:别被「苹果封装」迷惑了
CoreData是个好东西,但它默认的SQLite存储文件,也是明文的。
很多人觉得「我用CoreData了,数据应该安全了吧?」——其实不然。CoreData只是一个ORM框架,底层还是SQLite。那个.sqlite文件,用SQLite浏览器就能直接打开。
那怎么加密CoreData?我常用的方案有两种:
| 方案 | 原理 | 优缺点 |
|---|---|---|
| SQLCipher + CoreData | 用SQLCipher替换底层SQLite库,CoreData通过自定义NSPersistentStore来使用加密数据库 | ✅ 透明加密,代码改动小 ❌ 需要引入SQLCipher库,配置稍复杂 |
| 文件级加密(NSFileProtection) | 对CoreData的.sqlite文件设置NSFileProtectionComplete | ✅ 系统级加密,无需额外库 ❌ 设备锁定时无法读写,后台任务受限 |
我个人更推荐第一种方案。为什么?因为文件级加密虽然简单,但有个坑:当设备被锁定时,你的App在后台可能无法读写数据库。比如你做一个后台数据同步的任务,锁屏后就挂了。
用SQLCipher的话,你可以自己控制密钥的生命周期。我记得有一次,客户要求数据库密钥必须从服务器动态获取,不能硬编码。用SQLCipher配合Keychain存储密钥,完美解决。
4.3 SQLite数据库加密:SQLCipher实战
SQLCipher是目前iOS上最主流的SQLite加密方案。它基于SQLite,增加了256位AES加密。
使用起来其实不复杂,核心步骤就这几步:
// 1. 引入SQLCipher库(CocoaPods或SPM)
// pod 'SQLCipher'
// 2. 打开数据库时设置密钥
sqlite3_open("encrypted.db", &db);
sqlite3_key(db, "your_secret_key_32bytes", 32);
// 3. 之后的操作和普通SQLite完全一样
sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS users (id INTEGER, name TEXT)", NULL, NULL, NULL);
// 4. 关闭数据库
sqlite3_close(db);
这里有个关键点:密钥怎么管理?
- 硬编码在代码里:最不推荐,反编译就能看到。
- 存在Keychain里:好一些,但Keychain本身也可能被攻击。
- 从服务器动态获取:最安全,但需要网络。
- 使用设备指纹+用户密码派生:我比较喜欢这种方式。用用户的密码+设备唯一标识,通过PBKDF2派生出一个密钥。这样即使数据库文件被拿走,没有用户密码也解不开。
我曾经帮一个团队排查问题,他们发现数据库突然打不开了。查了半天,原来是密钥生成逻辑里用了一个设备的UUID,但用户升级系统后UUID变了……嗯,从那以后我都在密钥生成逻辑里加了「版本号」和「迁移方案」。
4.4 文件保护等级:系统自带的「保险柜」
iOS提供了四种文件保护等级,说白了就是系统帮你加密文件,但解锁条件不同。
| 保护等级 | 描述 | 适用场景 |
|---|---|---|
| NSFileProtectionNone | 不加密,任何情况下都可读写 | 临时文件、缓存 |
| NSFileProtectionComplete | 设备锁定时不可读写 | 用户隐私数据、密钥文件 |
| NSFileProtectionCompleteUnlessOpen | 文件打开后,即使锁定也可继续读写 | 后台需要持续写入的文件 |
| NSFileProtectionCompleteUntilFirstUserAuthentication | 用户首次解锁后,一直可读写 | 大多数应用数据(默认等级) |
怎么用?很简单,创建文件时设置属性:
// 创建文件时设置保护等级
NSData *data = [@"敏感内容" dataUsingEncoding:NSUTF8StringEncoding];
[data writeToFile:filePath
options:NSDataWritingFileProtectionComplete
error:nil];
// 或者对已有文件修改保护等级
[[NSFileManager defaultManager] setAttributes:@{
NSFileProtectionKey: NSFileProtectionComplete
} ofItemAtPath:filePath error:nil];
这里有个容易被忽略的点:文件保护等级只在设备设置了锁屏密码时才生效。如果用户没设密码,那这些保护等级形同虚设。所以,如果你的App对安全性要求极高,建议在启动时检测设备是否设置了密码,如果没有,提示用户去设置。
4.5 综合建议:四层防护体系
说了这么多,总结一下我个人在项目中的实践:
- NSUserDefaults:只存非敏感配置。敏感数据用Keychain或加密后存储。
- CoreData/SQLite:使用SQLCipher进行全库加密。密钥用PBKDF2从用户密码派生,并存储在Keychain中。
- 文件保护等级:对所有敏感文件设置NSFileProtectionComplete。
- 额外加固:对数据库文件、密钥文件,再叠加一层文件保护等级。双重保险。
你想想看,如果黑客拿到了你的App沙盒文件,他需要:
- 绕过文件保护等级(需要设备解锁)
- 破解SQLCipher的AES-256加密(需要密钥)
- 从Keychain中提取密钥(需要越狱或漏洞)
这三层下来,大部分攻击者就放弃了。安全不是绝对的,而是让攻击成本远大于收益。
好了,这一章就到这里。下一章咱们聊聊网络传输安全——HTTPS是不是真的就安全了?中间人攻击到底怎么防?到时候见。