数据存储安全:SharedPreferences、SQLite、File 的加密与权限控制
大家好,我是你们的老朋友。今天我们来聊聊 Android 数据存储安全。说实话,这块内容看起来基础,但坑特别多。我见过太多应用因为存储不当,导致用户隐私数据裸奔。嗯,咱们今天就把 SharedPreferences、SQLite 和 File 存储的加密与权限控制彻底讲透。
一、SharedPreferences:别把敏感数据当明文存
SharedPreferences 是 Android 最常用的轻量级存储方案。但很多人不知道,它默认是明文存储的。你想想看,如果手机被 root 了,或者有恶意应用通过 ADB 备份,你的 token、密码就全暴露了。
我个人习惯的做法是:敏感数据绝对不用明文存。比如用户登录后的 token、支付密码等,必须加密后再写入。
加密方案:EncryptedSharedPreferences
Android Jetpack 提供了 EncryptedSharedPreferences,它使用 AES256 加密 key,AES256-GCM 加密 value。我建议你直接用它,别自己造轮子。
// 初始化加密的 SharedPreferences
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
// 写入加密数据
sharedPreferences.edit().putString("user_token", "your_token_here").apply();
// 读取加密数据
String token = sharedPreferences.getString("user_token", null);
我曾经在项目中遇到过一个问题:某个同事把用户的身份证号直接存到普通 SharedPreferences 里。结果测试时发现,通过 adb shell 就能看到明文数据。后来我们全部迁移到了 EncryptedSharedPreferences,才解决了这个隐患。
二、SQLite 数据库:加密与权限控制
SQLite 是 Android 本地数据库的标配。但默认情况下,数据库文件也是明文存储的。如果你的应用存储了聊天记录、通讯录等敏感数据,不加密就等于裸奔。
2.1 数据库文件权限
默认创建的数据库文件权限是 660,只有应用自身和同 UID 的应用可以访问。但如果你使用了 MODE_WORLD_READABLE 或 MODE_WORLD_WRITEABLE(已废弃),那就危险了。
// 错误示例:不要这样做!
SQLiteDatabase db = openOrCreateDatabase("mydb.db", MODE_WORLD_READABLE, null);
// 正确做法:使用默认的 MODE_PRIVATE
SQLiteDatabase db = openOrCreateDatabase("mydb.db", MODE_PRIVATE, null);
2.2 数据库加密:SQLCipher
SQLCipher 是 SQLite 的加密扩展,它使用 AES256 加密整个数据库文件。我建议所有存储敏感数据的应用都使用它。
// 使用 SQLCipher 打开加密数据库
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
databaseFile,
"your_password_here",
null
);
// 创建表
database.execSQL("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, phone TEXT)");
// 插入数据
ContentValues values = new ContentValues();
values.put("name", "张三");
values.put("phone", "13800138000");
database.insert("users", null, values);
我记得有一次做安全审计,发现某个金融类应用把数据库密码写在了 BuildConfig 里。反编译后直接就能拿到密码。嗯,这种错误太低级了,但确实有人犯。
2.3 数据库内容加密
除了整个数据库加密,你还可以对敏感字段单独加密。比如用户的手机号、身份证号,可以在写入前用 AES 加密,读取时再解密。
// 对敏感字段单独加密
public void savePhoneNumber(String phone) {
String encryptedPhone = AESUtils.encrypt(phone, secretKey);
ContentValues values = new ContentValues();
values.put("phone", encryptedPhone);
database.insert("users", null, values);
}
public String getPhoneNumber(int userId) {
Cursor cursor = database.query("users", new String[]{"phone"}, "id=?", new String[]{String.valueOf(userId)}, null, null, null);
if (cursor.moveToFirst()) {
String encryptedPhone = cursor.getString(0);
return AESUtils.decrypt(encryptedPhone, secretKey);
}
return null;
}
三、File 存储:内部存储 vs 外部存储
文件存储是 Android 最灵活的存储方式,但也是最容易出问题的。我见过太多应用把敏感数据存到外部存储,结果被其他应用随意读取。
3.1 内部存储
内部存储的文件默认只能被当前应用访问。路径是 /data/data/包名/files/。这是最安全的存储位置。
// 写入内部存储
File file = new File(context.getFilesDir(), "secret.txt");
FileOutputStream fos = new FileOutputStream(file);
fos.write("敏感数据".getBytes());
fos.close();
// 读取内部存储
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[fis.available()];
fis.read(data);
fis.close();
3.2 外部存储
外部存储的文件默认是全局可读的。如果你把敏感数据存到外部存储,任何应用都可以读取。除非你使用 Context.getExternalFilesDir(),它会在外部存储的应用私有目录下创建文件,权限相对安全。
- 敏感数据:使用内部存储,并加密文件内容
- 非敏感数据(如缓存图片):使用外部存储的私有目录
- 绝对不要:把敏感数据存到外部存储的公共目录(如 Downloads)
3.3 文件加密
对于内部存储的文件,我建议也进行加密。因为如果手机被 root,内部存储的文件同样可以被读取。
// 使用 AES 加密文件内容
public void saveEncryptedFile(Context context, String fileName, String data) {
try {
File file = new File(context.getFilesDir(), fileName);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(secretKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedData = cipher.doFinal(data.getBytes());
FileOutputStream fos = new FileOutputStream(file);
fos.write(encryptedData);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
四、知识体系总览
下面这张图总结了本章的核心内容。你可以看到,数据存储安全主要围绕三个维度:加密、权限控制和存储位置选择。
五、避坑指南
最后,我总结几个常见的坑,都是我曾经踩过的:
- 坑一:把数据库密码写在代码里。反编译后直接暴露。解决方案:从服务器动态获取或使用 Keystore。
- 坑二:使用 MODE_WORLD_READABLE 共享文件。这等于把数据送给所有应用。解决方案:使用 ContentProvider。
- 坑三:外部存储存敏感数据。即使使用 getExternalFilesDir(),在 Android 10 以下仍然有风险。解决方案:敏感数据一律用内部存储。
- 坑四:自己实现加密算法。很多人用简单的 XOR 或 Base64 伪装加密。这跟没加密一样。解决方案:使用成熟的加密库。
好了,今天的内容就到这里。数据存储安全是 Android 安全的基础,但也是最容易被忽视的。希望你能把这些原则应用到实际项目中,别让用户的数据裸奔。