数据库安全最佳实践:SQL注入防护、敏感数据加密与更多

说实话,数据库安全这个话题,很多开发者容易忽视。尤其是做客户端开发,总觉得「数据在本地,能有什么问题?」。嗯,我以前也这么想,直到有一次在项目中看到用户密码明文存储在SQLite里……那感觉,就像看到自家大门敞开着,钥匙挂在门上。

今天我们就来聊聊Android SQLite的安全实践。我会结合自己踩过的坑,把SQL注入防护、敏感数据加密、文件权限、备份安全、混淆与加固这几个点,一个一个说清楚。

Android SQLite 安全实践知识体系 SQLite 安全实践 SQL注入防护 参数化查询 敏感数据加密 AES / SQLCipher 数据库文件权限 MODE_PRIVATE 备份安全 allowBackup 配置 混淆与加固

1. SQL注入防护:别让字符串拼接毁了你的数据库

先问个问题:你写SQL的时候,是不是习惯用字符串拼接?比如这样:

// 危险写法!千万别学
String name = editText.getText().toString();
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
db.rawQuery(sql, null);

这代码看着简单,但隐患极大。如果用户输入 ' OR 1=1 --,你的SQL就变成了:

SELECT * FROM users WHERE name = '' OR 1=1 --'

好家伙,整个表的数据全出来了。我在项目中就遇到过类似的事——测试同学随手输入了一个单引号,App直接崩溃。后来一查,是拼接SQL没做任何处理。

正确做法:使用参数化查询

// 安全写法
String sql = "SELECT * FROM users WHERE name = ?";
Cursor cursor = db.rawQuery(sql, new String[]{name});

或者用 SQLiteStatement

SQLiteStatement stmt = db.compileStatement(
    "INSERT INTO users(name, age) VALUES(?, ?)"
);
stmt.bindString(1, name);
stmt.bindLong(2, age);
stmt.executeInsert();

说白了,参数化查询就是把用户输入当成「数据」而不是「SQL代码」来处理。数据库引擎会自动转义特殊字符,注入就无从下手了。

我的习惯:所有涉及用户输入的SQL,一律用参数化。哪怕只是查询一个ID,也别偷懒拼接字符串。养成习惯后,基本不会出注入问题。

2. 敏感数据加密:别把密码当「明文宝贝」

我记得有一次做代码审查,看到同事把用户手机号直接存到SQLite里。我说:「这数据要是被拿到,用户隐私全暴露了。」他反问:「本地数据库谁会看?」

嗯,你想想看——只要手机被root,或者有人通过adb备份,数据库文件就能被直接读取。所以敏感数据必须加密。

我个人推荐两种方案:

  • 字段级加密:对敏感字段单独加密,比如用AES加密用户手机号、身份证号。
  • 全库加密:使用SQLCipher,整个数据库文件都是加密的。

字段级加密示例:

// 加密工具类(简化版)
public class CryptoUtil {
    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final byte[] KEY = ...; // 密钥从KeyStore获取

    public static String encrypt(String plainText) {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] iv = cipher.getIV();
        byte[] encrypted = cipher.doFinal(plainText.getBytes());
        // 将iv和密文拼接存储
        return Base64.encodeToString(iv, Base64.NO_WRAP) + ":" +
               Base64.encodeToString(encrypted, Base64.NO_WRAP);
    }
}

如果项目对安全性要求高,直接用SQLCipher。它是对整个SQLite文件加密,使用方式几乎一样:

SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
    "encrypted.db", "your_password", null
);

注意:密钥不要硬编码在代码里。建议用Android KeyStore存储密钥,或者从服务器动态获取。我曾经见过把密钥写在strings.xml里的……那加密等于没加。

3. 数据库文件权限:别让其他App偷看你的数据

Android的数据库文件默认存储在 /data/data/包名/databases/ 目录下。正常情况下,其他App无法访问。但如果你创建数据库时用了 MODE_WORLD_READABLEMODE_WORLD_WRITEABLE,那就等于把家门敞开了。

这些模式在API 17以后已经被废弃,但老项目里可能还有遗留。检查一下你的代码:

// 错误示范
db = openOrCreateDatabase("mydb.db", MODE_WORLD_READABLE, null);

// 正确做法
db = openOrCreateDatabase("mydb.db", MODE_PRIVATE, null);

另外,如果你用 SQLiteOpenHelper,默认就是私有模式,不用额外操心。但要注意一点:数据库文件所在的目录权限也要正确。我遇到过一个问题——手动创建了databases目录,但权限设置成了755,导致数据库文件虽然私有,但目录可读。嗯,细节决定成败。

4. 备份安全:别让adb backup把你的数据全带走

Android提供了 android:allowBackup 属性,默认是true。这意味着用户可以通过adb backup命令,把整个App的数据(包括SQLite数据库)备份到电脑上。

你想想看,如果用户的手机丢了,或者有人恶意备份,数据库里的数据就全暴露了。

解决方案:

  • 在AndroidManifest.xml中设置 android:allowBackup="false",禁止备份。
  • 如果必须支持备份,使用 android:fullBackupContent 指定只备份非敏感数据。
<application
    android:allowBackup="false"
    android:fullBackupContent="@xml/backup_rules">
    ...
</application>

res/xml/backup_rules.xml 中:

<full-backup-content>
    <exclude domain="database" path="users.db"/>
    <exclude domain="sharedpref" path="secure_prefs.xml"/>
</full-backup-content>

我曾经踩过的坑:有个项目上线后,用户反馈说「换手机后数据没了」。一查,是因为allowBackup设成了false,用户迁移数据时没备份到。后来我们改成了只排除敏感表,其他数据正常备份。安全与体验要平衡。

5. 混淆与加固:让逆向工程师多费点功夫

代码混淆不能直接保护数据库,但它能让攻击者更难分析你的代码逻辑。比如,你的数据库操作类、加密工具类,如果被混淆了,别人反编译后看到的是一堆 a.b.c(),想找到关键逻辑就得花不少时间。

proguard-rules.pro 中,保留数据库相关的类不被混淆:

-keep class com.yourpackage.db.** { *; }
-keep class * extends android.database.sqlite.SQLiteOpenHelper { *; }
-keep class * implements android.database.Cursor { *; }

但要注意,如果你用了Room或GreenDAO等ORM框架,它们的混淆规则要单独配置。我建议把数据库相关的实体类、DAO类都保留,否则运行时可能报错。

除了混淆,还可以考虑加固。市面上有360加固、腾讯加固等方案,它们会对DEX文件进行加壳、反调试等处理。加固后,即使有人拿到APK,也很难直接反编译出原始代码。

总结一下我的安全清单:

  1. 所有SQL用参数化查询,杜绝拼接。
  2. 敏感数据用AES或SQLCipher加密。
  3. 数据库文件权限设为MODE_PRIVATE。
  4. 合理配置allowBackup,敏感数据不备份。
  5. 代码混淆+加固,增加逆向难度。

安全这件事,没有「绝对安全」,只有「相对更难攻破」。你多做一层防护,攻击者就多一道门槛。很多数据泄露事件,其实都是因为开发者忽略了这些基础实践。希望今天的分享能帮你少踩几个坑。


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