一、数据存储与加密:本地数据加密、密钥管理与最小化存储

说实话,Android 开发这么多年,我踩过最深的坑就是数据存储。你以为存在手机里就安全了?太天真了。SharedPreferences 明文存密码、SQLite 裸奔存用户信息,这些我都在项目里见过。今天咱们就聊聊怎么把本地数据真正锁起来。

1.1 本地数据加密:别让数据裸奔

先说说最常见的两个场景:键值对存储和数据库存储。Android 官方其实给了我们现成的方案,但很多人不知道,或者懒得用。

EncryptedSharedPreferences:官方推荐

SharedPreferences 默认是明文存储的。root 过的手机,随便一个文件管理器就能看到你的数据。我有个朋友,他们的 app 把用户 token 直接存 SP,结果被用户投诉隐私泄露……

用 EncryptedSharedPreferences 其实很简单:

// 初始化
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", "xxx").apply();

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

你看,代码几乎没变,只是换了个类。但背后做了两件事:密钥加密值加密。密钥用 AES256-SIV,值用 AES256-GCM。说白了,就算有人拿到文件,也解不开。

我的习惯:所有敏感信息,包括 token、用户偏好设置、缓存的关键数据,一律用 EncryptedSharedPreferences。别嫌麻烦,出事就晚了。

SQLCipher:给 SQLite 加把锁

如果你用 Room 或者原生 SQLite,数据也是明文的。SQLCipher 是 SQLite 的加密扩展,能对整个数据库文件加密。

集成方式:

// build.gradle
implementation 'net.zetetic:android-database-sqlcipher:4.5.6'

// 使用
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
    "data.db", 
    "your_password", 
    null
);

嗯,这里要注意:密码不能硬编码。我见过有人直接把密码写在代码里,那跟没加密有什么区别?密码应该从 Android Keystore 里取,或者用生物识别解锁。

我曾经踩过的坑:有一次我把 SQLCipher 密码写死在代码里,结果代码被反编译,用户数据全暴露了。后来我改成从 Keystore 动态获取,才算真正安全。

1.2 密钥管理:Android Keystore 才是亲爹

加密的密钥放哪?这是个灵魂问题。你放文件里,别人能读;你放代码里,别人能反编译。Android Keystore 是硬件级别的安全存储,密钥一旦生成,就无法从设备中导出

为什么这么牛?因为密钥存在 TEE(可信执行环境)里,app 进程根本拿不到密钥明文。你只能用它加密解密,但不能把它拷走。

使用示例:

// 生成密钥
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
    "my_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();

// 加密
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKey key = (SecretKey) keyStore.getKey("my_key", null);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedData = cipher.doFinal(plaintext.getBytes());

你想想看,密钥存在 Keystore 里,就算 app 被脱壳、被 hook,攻击者也拿不到密钥。这才是真正的安全感。

核心原则:密钥永远不要出现在代码里、文件里、网络里。只用 Keystore 生成和存储。

1.3 数据最小化存储策略:能不存就不存

加密再强,也不如不存。GDPR 和个人信息保护法都强调数据最小化原则。说白了,你只存业务必须的数据,其他的一律不碰。

我总结了几条实操建议:

  • 能不存就不存:比如用户位置,用完就删,别留着做分析
  • 能脱敏就脱敏:手机号存后四位,身份证只存哈希
  • 能过期就过期:设置 TTL,到期自动清理
  • 能本地就不上传:敏感数据尽量留在本地,别往服务器传

举个例子:你的 app 需要用户手机号做登录。你可以只存手机号的 SHA-256 哈希,用于校验。真正的手机号,用完就丢。这样就算数据库泄露,攻击者也拿不到原始手机号。

我个人习惯:每次新增一个存储字段,先问自己三个问题——真的需要吗?能脱敏吗?能设置过期吗?三个问题都通过,才存。

知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以看到,数据存储、加密、密钥管理、最小化策略,其实是环环相扣的。

Android 本地数据安全架构 本地数据安全 数据存储 EncryptedSharedPreferences SQLCipher 加密数据库 密钥管理 Android Keystore TEE 硬件隔离 加密算法 AES-256 GCM/SIV 密钥永不导出 数据最小化策略 能不存不存 能脱敏脱敏 能过期过期

你看,整个架构分四层:数据存储负责加密落地,密钥管理负责保护钥匙,加密算法提供技术保障,最小化策略从源头减少风险。四者缺一不可。

一句话总结:用 EncryptedSharedPreferences 和 SQLCipher 加密数据,用 Android Keystore 管理密钥,用最小化策略减少存储。做到这三点,你的 app 在数据安全上就及格了。

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