数据存储安全:SharedPreferences加密、SQLite数据库加密、文件存储权限控制、KeyStore使用

数据存储安全,说白了就是你的App把数据存到设备上时,不能被别人轻易拿走。我见过太多开发者在存储敏感信息时,直接明文写入SharedPreferences,结果反编译APK后,账号密码一览无余。嗯,这章我们就来聊聊怎么把数据存得踏实。

核心原则:敏感数据必须加密存储,密钥必须由系统级安全容器保管,文件权限必须遵循最小化原则。

SharedPreferences加密:别让明文成为习惯

SharedPreferences是Android最常用的轻量级存储方案。但默认情况下,它存的是明文。你想想看,如果里面放了token、用户手机号、甚至支付密码,那跟把钥匙挂在门外有什么区别?

我个人习惯使用EncryptedSharedPreferences,这是AndroidX Security库提供的方案。它底层用AES-256加密数据,密钥由KeyStore托管。用起来其实很简单:

// 引入依赖:implementation 'androidx.security:security-crypto:1.1.0-alpha06'

String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);

SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
    "secure_prefs",
    masterKeyAlias,
    context,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

// 存数据
sharedPreferences.edit().putString("user_token", "eyJhbGciOiJIUzI1NiIs...").apply();

// 取数据
String token = sharedPreferences.getString("user_token", null);

避坑指南:我曾经在项目里直接用AES加密value,但key还是明文。后来发现攻击者只要拿到key,加密形同虚设。EncryptedSharedPreferences连key一起加密,这才是正确的姿势。

另外,如果你的App需要兼容Android 6.0以下设备,记得检查KeyStore是否可用。我建议在初始化时加个try-catch,降级到使用AndroidKeyStore或BouncyCastle提供者。

SQLite数据库加密:Room + SQLCipher的组合拳

SQLite数据库是结构化数据的首选。但默认的SQLite没有加密能力,数据库文件被拖走后,用SQLite浏览器就能直接打开。这太危险了。

我推荐使用SQLCipher,它是SQLite的加密扩展,使用256-bit AES加密整个数据库文件。配合Room使用,几乎不需要改业务代码:

// 1. 添加依赖
// implementation 'net.zetetic:android-database-sqlcipher:4.5.0'
// implementation 'androidx.room:room-runtime:2.4.0'

// 2. 创建加密的Room数据库
byte[] passphrase = SQLiteDatabase.getBytes("your_strong_password".toCharArray());
SupportFactory factory = new SupportFactory(passphrase);

AppDatabase db = Room.databaseBuilder(context, AppDatabase.class, "app.db")
    .openHelperFactory(factory)
    .build();

// 3. 使用方式完全不变
User user = db.userDao().getUserById(1);

注意:密码不要硬编码在代码里!我见过有人把密码写在BuildConfig里,结果ProGuard一混淆,反编译后还是能看到。正确的做法是从KeyStore获取密钥,或者使用用户输入的PIN码派生。

为什么推荐SQLCipher而不是自己实现加密?说白了,数据库加密不是简单的「加密字段值」就完事了。索引、排序、事务日志都需要处理。SQLCipher是经过安全审计的成熟方案,比自己造轮子靠谱得多。

文件存储权限控制:内外有别

Android文件存储分为内部存储和外部存储。内部存储每个App有独立的沙箱,其他App默认无法访问。外部存储则共享空间,权限控制要格外小心。

存储类型 路径示例 权限控制 推荐用途
内部存储 /data/data/包名/ 仅本App可读写 敏感数据、缓存
外部存储(私有) /storage/emulated/0/Android/data/包名/ 本App可读写,其他App需权限 大文件、媒体文件
外部存储(公共) /storage/emulated/0/Download/ 所有App可读写 用户主动分享的文件

我个人的建议是:能用内部存储就别用外部存储。如果必须用外部存储,优先使用私有目录(Android/data/包名/),这样卸载App时系统会自动清理。

对于Android 10及以上,推荐使用Scoped Storage。它限制了App对外部存储的访问范围,即使申请了权限,也不能随意读取其他App的文件。嗯,这是Google在安全上做的好事。

// Android 10+ 推荐方式:使用MediaStore或SAF
// 保存图片到公共目录
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "photo.jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);

Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
// 通过uri写入文件流

KeyStore使用:密钥的保险箱

前面提到的加密方案,最终都绕不开一个问题:密钥放哪儿?放在代码里?放在资源文件里?都不安全。Android KeyStore就是来解决这个问题的——它是一个硬件/软件隔离的安全容器,密钥一旦生成,就无法从进程中导出。

我习惯用KeyStore来生成和存储AES密钥,配合EncryptedSharedPreferences或SQLCipher使用:

// 生成AES密钥并存入KeyStore
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
        "my_aes_key",
        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();

// 使用密钥进行加密
SecretKey key = (SecretKey) keyStore.getKey("my_aes_key", null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] encryptedData = cipher.doFinal(plaintext.getBytes());

关键点:KeyStore中的密钥无法被导出,即使App被root,攻击者也拿不到密钥原文。加密时生成的IV(初始化向量)需要和密文一起存储,解密时要用到。

为什么KeyStore这么安全?因为它利用了TEE(可信执行环境)或SE(安全元件)。说白了,密钥运算在独立的硬件区域完成,App进程只能拿到加密后的结果,永远碰不到密钥本身。

我曾经在一个金融类项目里,把用户支付密码的派生密钥存在KeyStore中。即使设备被root,攻击者也无法通过内存dump拿到密钥。嗯,这才是真正的「数据存储安全」。

小技巧:KeyStore在Android 4.3引入,但直到Android 6.0才支持AES密钥。如果你的App需要兼容低版本,可以考虑使用Conceal库(Facebook出品)作为替代方案。

数据存储安全知识体系 数据存储安全 SharedPreferences SQLite数据库 文件存储 Android KeyStore EncryptedSP AES-256加密 SQLCipher Room集成 内部存储 Scoped Storage 密钥不可导出 TEE硬件隔离

总结一下这四块内容:SharedPreferences加密解决的是键值对存储的明文问题;SQLite加密保护的是结构化数据;文件存储权限控制管的是文件级别的访问;KeyStore则是所有密钥的最终保管者。四者配合使用,才能构建完整的数据存储安全防线。

嗯,数据存储安全这块,说白了就是「存得隐蔽、加密彻底、密钥安全」。别等到用户数据泄露了才后悔,那时候就晚了。

公众号:蓝海资料掘金营,微信deep3321