4、本地数据安全:NSUserDefaults安全、CoreData加密、SQLite数据库加密、文件保护等级

本地数据安全,说白了就是你的App在用户手机里存的东西,到底安不安全。

我见过太多开发者,把用户密码、Token直接往NSUserDefaults里一塞,就觉得万事大吉了。嗯,这其实跟把家门钥匙放在门口脚垫下面差不多——防君子不防小人。

今天咱们就聊聊,iOS本地存储的四个核心环节,怎么才能真正做到「安全」。

4.1 NSUserDefaults:别让它成为你的「数据裸奔」入口

NSUserDefaults,苹果官方说它是「轻量级持久化存储」。我个人习惯叫它「明文小仓库」——因为它默认就是明文存储的。

你想想看,一个plist文件,放在App的Library/Preferences目录下,用iTools或者任何文件查看器,连越狱都不用,就能直接打开看。

⚠️ 警告: 绝对不要在NSUserDefaults中存储密码、Token、密钥等敏感信息。它没有加密机制,且容易被备份和恢复。

那怎么办?我的做法是:

  • 只存非敏感配置:比如用户偏好设置、界面状态、是否第一次启动。
  • 敏感数据必须加密后再存:用AES加密后,把密文存进去。
  • 开启NSUserDefaults的Keychain备份?别想了,它不支持。

我曾经在一个金融类App的审计中,发现开发者把用户的身份证号直接存到了NSUserDefaults里。我问为什么,他说「方便读取」。嗯,方便了黑客也方便了你。

💡 小技巧: 如果你实在要用NSUserDefaults存敏感数据,至少用CommonCrypto做一层AES-256加密。但说实话,我更推荐直接用Keychain。

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存储密钥,完美解决。

🔑 核心要点: CoreData本身不提供加密能力。你需要通过SQLCipher或文件保护等级来加固。别指望苹果帮你做这件事。

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派生出一个密钥。这样即使数据库文件被拿走,没有用户密码也解不开。
⚠️ 注意: SQLCipher的密钥一旦丢失或错误,数据库将永久无法打开。没有「找回密码」功能。所以密钥管理一定要做好备份方案。

我曾经帮一个团队排查问题,他们发现数据库突然打不开了。查了半天,原来是密钥生成逻辑里用了一个设备的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];
💡 我的建议: 对于数据库文件、密钥文件、用户隐私数据,一律使用NSFileProtectionComplete。虽然设备锁定时不能读写,但安全性提升了一个量级。如果App有后台任务需要读写,考虑用NSFileProtectionCompleteUnlessOpen。

这里有个容易被忽略的点:文件保护等级只在设备设置了锁屏密码时才生效。如果用户没设密码,那这些保护等级形同虚设。所以,如果你的App对安全性要求极高,建议在启动时检测设备是否设置了密码,如果没有,提示用户去设置。

4.5 综合建议:四层防护体系

说了这么多,总结一下我个人在项目中的实践:

  1. NSUserDefaults:只存非敏感配置。敏感数据用Keychain或加密后存储。
  2. CoreData/SQLite:使用SQLCipher进行全库加密。密钥用PBKDF2从用户密码派生,并存储在Keychain中。
  3. 文件保护等级:对所有敏感文件设置NSFileProtectionComplete。
  4. 额外加固:对数据库文件、密钥文件,再叠加一层文件保护等级。双重保险。

你想想看,如果黑客拿到了你的App沙盒文件,他需要:

  • 绕过文件保护等级(需要设备解锁)
  • 破解SQLCipher的AES-256加密(需要密钥)
  • 从Keychain中提取密钥(需要越狱或漏洞)

这三层下来,大部分攻击者就放弃了。安全不是绝对的,而是让攻击成本远大于收益。

📌 记住: 本地数据安全没有银弹。NSUserDefaults、CoreData、SQLite、文件保护等级,每一层都有它的作用和局限。组合使用,才能构建真正的安全防线。

好了,这一章就到这里。下一章咱们聊聊网络传输安全——HTTPS是不是真的就安全了?中间人攻击到底怎么防?到时候见。