22、数据存储安全:SharedPreferences、SQLite数据库、Keychain/Keystore、文件加密分析
说到移动App的数据存储安全,我脑子里立刻浮现出几年前的一个项目。那时候刚入行,觉得数据存到本地就完事了,结果被安全测试报告怼得满头包。嗯,今天咱们就来聊聊这个绕不开的话题——你的App到底该怎么安全地存数据。
22.1 SharedPreferences:别把秘密放这里
SharedPreferences,说白了就是个轻量级的键值对存储。很多新手喜欢用它存用户Token、密码,甚至支付信息。我见过最夸张的,有人把信用卡CVV码直接存进去了。
那怎么办?我个人习惯是:
- 只存非敏感配置项,比如界面主题、语言设置
- 如果非要存敏感数据,至少用AES加密后再存
- 别用「记住密码」功能,除非你做好了加密
我曾经在一个金融类App里看到,他们把用户登录态直接存成明文。你想想看,这要是被中间人或者恶意应用读到,后果不堪设想。
22.2 SQLite数据库:加密是必修课
SQLite是移动端最常用的关系型数据库。Android和iOS都内置了它。但默认情况下,数据库文件也是明文的。
我记得有一次做逆向分析,拿到一个App的apk,解压后直接找到.db文件,用SQLite浏览器打开,用户聊天记录、通讯录全在里面。说实话,那一刻我挺震惊的——这App的用户量还不少。
22.2.1 Android上的SQLCipher
Android上我推荐用SQLCipher。它是SQLite的加密扩展,支持256位AES加密。用法也很简单:
// 初始化加密数据库
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
databaseFile,
"your_password_here",
null
);
// 或者使用Room + SQLCipher
SupportFactory factory = new SupportFactory(
"your_password_here".getBytes()
);
Room.databaseBuilder(context, AppDatabase.class, "app.db")
.openHelperFactory(factory)
.build();
22.2.2 iOS上的Core Data加密
iOS上,你可以用Data Protection API。只要在Core Data的持久化存储选项中设置NSFileProtectionComplete:
let storeOptions = [
NSPersistentHistoryTrackingKey: true,
NSPersistentStoreFileProtectionKey:
FileProtectionType.complete
]
try persistentStoreCoordinator.addPersistentStore(
ofType: NSSQLiteStoreType,
configurationName: nil,
at: storeURL,
options: storeOptions
)
这样数据库文件就会被系统级的文件加密保护。锁屏状态下,谁也读不了。
22.3 Keychain/Keystore:这才是存密钥的地方
很多开发者分不清「加密」和「存密钥」的区别。你加密了数据,但密钥放哪儿?放SharedPreferences?那等于没加密。
Android Keystore和iOS Keychain,就是专门干这个的。它们把密钥放在硬件安全模块(TEE/SE)里,App本身都读不到密钥的明文。
22.3.1 Android Keystore
// 生成密钥
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"my_key_alias",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build();
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
);
keyGenerator.init(spec);
keyGenerator.generateKey();
22.3.2 iOS Keychain
// 存储密钥到Keychain
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "my_key_alias",
kSecValueData as String: keyData,
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemAdd(query as CFDictionary, nil)
我个人习惯是:所有敏感密钥、Token、证书,一律走Keychain/Keystore。别嫌麻烦,这是底线。
22.4 文件加密:别只依赖系统权限
很多App把文件直接存到外部存储(SD卡)或者App私有目录。但私有目录就安全吗?不一定。root过的设备,或者有adb backup权限,照样能读。
我建议的做法是:
- 敏感文件必须加密后再写入磁盘
- 使用AES-GCM模式,带认证标签,防止篡改
- 密钥从Keychain/Keystore获取,不要硬编码
// Android文件加密示例
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKey key = getKeyFromKeystore("my_key_alias");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV(); // 保存IV,解密时需要
byte[] cipherText = cipher.doFinal(plainText);
// 写入文件时,把IV和密文一起存
FileOutputStream fos = new FileOutputStream(file);
fos.write(iv);
fos.write(cipherText);
fos.close();
22.5 知识体系总览
下面这张图,是我梳理的数据存储安全核心逻辑。你可以把它当成一个检查清单:
22.6 避坑指南
最后,分享几个我踩过的坑:
- 别用「加密」代替「安全」——加密算法选错了,比不加密更可怕。比如用DES、RC4这些老古董。
- 密钥管理是核心——我曾经见过有人把AES密钥写在Java代码的静态变量里。反编译一下,密钥直接暴露。
- 别忘了清理内存——加密后的数据用完了,记得把byte[]置空。Java里虽然GC会回收,但敏感数据在内存里停留越久越危险。
- 日志里别打印敏感数据——这个我犯过。调试时为了方便,把解密后的数据log出来了,结果上线前忘了删。被安全团队抓了个正着。
好了,今天就聊到这儿。记住,安全不是功能,是习惯。养成好习惯,比学再多技巧都管用。
公众号:蓝海资料掘金营,微信deep3321