AndroidRoom数据库加密详解


本文整理自网络,侵删。

本文实例为大家分享了Android Room之数据库加密的具体实现,供大家参考,具体内容如下

一、需求背景

Android平台自带的SQLite有一个致命的缺陷:不支持加密。这就导致存储在SQLite中的数据可以被任何人用任何文本编辑器查看到。如果是普通的数据还好,但是当涉及到一些账号密码,或者聊天内容的时候,我们的应用就会面临严重的安全漏洞隐患。

二、加密方案

1、在数据存储之前进行加密,在加载数据之后再进行解密,这种方法大概是最容易想的到,而且也不能说这种方式不好,就是有些比较繁琐。 如果项目有特殊需求的话,可能还需要对数据库的表明,列明也进行加密。

2、对数据库整个文件进行加密,好处就是就是无需在插入之前对数据加密,也无需在查询数据之后再解密。比较出名的第三方库就是SQLCipher,它采用的方式就是对数据库文件进行加密,只需在打开数据库的时候输入密码,之后的操作更正常操作没有区别。

三、Hook Room实现方式

前面说了,加密的方式一比较繁琐的地方是需要在存储数据之前加密,在检索数据之后解密,那么是否有一种方式在Room操作数据库的过程中,自动对数据加密解密,答案是有的。

Dao编译之后的代码是这样的:

@Override
public long saveCache(final CacheTest cache) {
? __db.assertNotSuspendingTransaction();
? __db.beginTransaction();
? try {
? //核心代码,绑定数据
? ? long _result = __insertionAdapterOfCacheTest.insertAndReturnId(cache);
? ? __db.setTransactionSuccessful();
? ? return _result;
? } finally {
? ? __db.endTransaction();
? }
}

__insertionAdapterOfCacheTest 是在CacheDaoTest_Impl 的构造方法里面创建的一个匿名内部类,这个匿名内部类实现了bind 方法

public CacheDaoTest_Impl(RoomDatabase __db) {
? this.__db = __db;
? this.__insertionAdapterOfCacheTest = new EntityInsertionAdapter<CacheTest>(__db) {
? ? @Override
? ? public String createQuery() {
? ? ? return "INSERT OR REPLACE INTO `table_cache` (`key`,`name`) VALUES (?,?)";
? ? }

? ? @Override
? ? public void bind(SupportSQLiteStatement stmt, CacheTest value) {
? ? ? if (value.getKey() == null) {
? ? ? ? stmt.bindNull(1);
? ? ? } else {
? ? ? ? stmt.bindString(1, value.getKey());
? ? ? }
? ? ? if (value.getName() == null) {
? ? ? ? stmt.bindNull(2);
? ? ? } else {
? ? ? ? stmt.bindString(2, value.getName());
? ? ? }
? ? }
? };
}

关于SQLiteStatement 不清楚的同学可以百度一下,简单说他就代表一句sql语句,bind 方法就是绑定sql语句所需要的参数,现在的问题是我们可否自定义一个SupportSQLiteStatement ,然后在bind的时候加密参数呢。

我们看一下SupportSQLiteStatement 的创建过程。

public SupportSQLiteStatement acquire() {
? ? ?assertNotMainThread();
? ? ?return getStmt(mLock.compareAndSet(false, true));
?}
?
?private SupportSQLiteStatement getStmt(boolean canUseCached) {
? ? ?final SupportSQLiteStatement stmt;
? ? ?//代码有删减
? ? ? ? stmt = createNewStatement();
? ? ?return stmt;
?}

kotlin
?private SupportSQLiteStatement createNewStatement() {
? ? ?String query = createQuery();
? ? ?return mDatabase.compileStatement(query);
?}

可以看到SupportSQLiteStatement 最终来自RoomDataBase的compileStatement 方法,这就给我们hook 提供了接口,我们只要自定义一个SupportSQLiteStatement 类来代理原来的SupportSQLiteStatement 就可以了。

encoder 就是用来加密数据的。

加密数据之后剩余的就是解密数据了,解密数据我们需要在哪里Hook呢?

我们知道数据库检索返回的数据一般都是通过Cursor 传递给用户,这里我们就可以通过代理数据库返回的这个Cursor 进而实现解密数据。

@Database(entities = [CacheTest::class], version = 3)
abstract class TestDb : RoomDatabase() {
? ? abstract fun testDao(): CacheDaoTest

? ? companion object {
? ? ? ? val MIGRATION_2_1: Migration = object : Migration(2, 1) {
? ? ? ? ? ? override fun migrate(database: SupportSQLiteDatabase) {
? ? ? ? ? ? }
? ? ? ? }

? ? ? ? val MIGRATION_2_3: Migration = object : Migration(2, 3) {
? ? ? ? ? ? override fun migrate(database: SupportSQLiteDatabase) {
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? val MIGRATION_3_4: Migration = object : Migration(3,4) {
? ? ? ? ? ? override fun migrate(database: SupportSQLiteDatabase) {
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? val MIGRATION_2_4: Migration = object : Migration(2, 4) {
? ? ? ? ? ? override fun migrate(database: SupportSQLiteDatabase) {
? ? ? ? ? ? }
? ? ? ? }

? ? }

? ? private val encoder: IEncode = TestEncoder()
? ? override fun query(query: SupportSQLiteQuery): Cursor {
? ? ? ? var cusrosr = super.query(query)
? ? ? ? println("开始查询1")
? ? ? ? return DencodeCursor(cusrosr, encoder)
? ? }

? ? override fun query(query: String, args: Array<out Any>?): Cursor {
? ? ? ? var cusrosr = super.query(query, args)
? ? ? ? println("开始查询2")
? ? ? ? return DencodeCursor(cusrosr, encoder)
? ? }

? ? override fun query(query: SupportSQLiteQuery, signal: CancellationSignal?): Cursor {
? ? ? ? println("开始查询3")
? ? ? ? return DencodeCursor(super.query(query, signal), encoder)
? ? }
}

我们这里重写了RoomDatabase 的是query 方法,代理了原先的Cursor 。

class DencodeCursor(val delete: Cursor, val encoder: IEncode) : Cursor {
//代码有删减
? ? override fun getString(columnIndex: Int): String {
? ? ? ? return encoder.decodeString(delete.getString(columnIndex))
? ? }
}

如上,最终加密解密的都被hook在了Room框架中间。但是这种有两个个缺陷

加密解密的过程中不可以改变数据的类型,也就是整型在加密之后还必须是整型,整型在解密之后也必须是整型。同时有些字段可能不需要加密也不需要解密,例如自增长的整型的primary key。其实这种方式也比较好解决,可以规定key 为整数型,其余的数据一律是字符串。这样所有的树数字类型的数据都不需要参与加密解密的过程。

sql 与的参数必须是动态绑定的,而不是在sql语句中静态指定。

@Query("select * from table_cache where `key`=:primaryKey")
fun getCache(primaryKey: String): LiveData<CacheTest>
@Query("select * from table_cache where `key`= '123' ")
fun getCache(): LiveData<CacheTest>

四、SQLCipher方式

SQLCipher 仿照官方的架构自己重写了一套代码,官方提供的各种数据库相关的类在SQLCipher 里面也是存在的而且名字都一样除了包名不同。

阅读剩余部分

相关阅读 >>

Sqlite数据库常用语句及mac上的Sqlite可视化工具meassqllite使用方法

详解python中executemany和序列的使用方法

python执行数据库的查询操作实例讲解

listview与Sqlite结合实现记事本功能

将django项目部署到centos的踩坑实战

mybatis如何自动生成sql语句

Sqlitestudio优雅调试android手机数据库Sqlite(推荐)

解析Sqlite中的常见问题与总结详解

android调试工具adb命令大全

python练习之操作Sqlite数据库

更多相关阅读请进入《Sqlite》频道 >>


数据库系统概念 第6版
书籍

数据库系统概念 第6版

机械工业出版社

本书主要讲述了数据模型、基于对象的数据库和XML、数据存储和查询、事务管理、体系结构等方面的内容。



打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...