Android DBFlow 的使用以及兼容老版本实现数据库加密

2,133 阅读2分钟

本文是对 DBFlow 的基础使用以及兼容老版本数据实现数据库加密的记录。

背景

为了用户数据安全,需要加密数据库中的数据。加密数据可以实现的方式有:

  1. 加密特定字段,存入的时候加密字段值,使用的时候解密;
  2. 使用 sqlcipher 加密整个数据库文件;

现状介绍

App 已经上线,有用户在用,所以要考虑加密之后已有数据的兼容性;

DBFlow 的使用

  1. 添加 gradle 依赖

    a. 项目 build.gradle 文件中添加:

    allprojects {
        repositories {
            maven { url "https://www.jitpack.io" }
        }
    }
    

    b. 模块 build.gradle 文件中添加:

    // DBFlow
    annotationProcessor 'com.github.Raizlabs.DBFlow:dbflow-processor:4.2.4'
    implementation "com.github.Raizlabs.DBFlow:dbflow-core:4.2.4"
    implementation "com.github.Raizlabs.DBFlow:dbflow:4.2.4"
    // DBFlow 加密
    implementation "com.github.Raizlabs.DBFlow:dbflow-sqlcipher:4.2.4"
    implementation "net.zetetic:android-database-sqlcipher:3.5.0@aar"
    
  2. 初始化 flow

    在 Application 的 onCreate 方法中初始化。

    初始化时可以指定:数据库名、OpenHelper 等。

    无需额外配置直接调用方法 FlowManager.init(this); 就完成类初始化操作。

    加密时的方法:

    FlowManager.init(FlowConfig.builder(this).addDatabaseConfig(
        DatabaseConfig.builder(AppDatabase.class)
                  .databaseName("dbflow_test")
                .openHelper(new DatabaseConfig.OpenHelperCreator() {
                    @Override
                    public OpenHelper createHelper(DatabaseDefinition databaseDefinition, DatabaseHelperListener helperListener) {
                        return new CustomOpenHelper(App.this, databaseDefinition, helperListener);
                    }
                })
                .build()
    ).build());
    
  3. 创建数据表

    新建类 User 继承于类 BaseModel,并在类中需要添加表、列注解。

    @Table(database = AppDatabase.class)
    public class User extends BaseModel {
        @PrimaryKey(autoincrement = true)
        @Column
        private long _id;
        
        @Column
        private int id;
    
        @Column
        private String name;
    }
    

    继承 BaseModel 是为了方便调用 save()、updage()、delte() 等方法进行相对应的数据操作。

添加 sqlcipher

DBFlow 使用 sqlcipher 很方便,只要初始化时设置自定义 OpenHelper(见上面初始化部分代码)。 类 CustomOpenHelper 继承自抽象类 SQLCipherOpenHelper,实现其中的方法 getCipherSecret,该方法中设置数据库密码。

已有数据的加密保存

备份逻辑:获取之前的数据库,创建附加数据库、执行加密操作、分离数据库。 备份时主要使用的sql命令:ATTACH、sqlcipher_export、DETACH。

代码如下:

    /**
     * 加密未加密的数据
     * 成功之后会删除旧 .db 文件,避免重复执行的可能
     */
    private void encryptOldData(Context context, String oldDBName, String password) throws Exception {
        Log.d(TAG, "backupOldData() ");
        File originalFile = context.getDatabasePath(oldDBName);
        // 如果旧数据库文件存在,则进行下面的加密备份操作
        if (originalFile.exists()) {
            Log.d(TAG, "exixts ");
            File targetFile = context.getDatabasePath(DB_NAME_NOW + ".db");
            File newFile = File.createTempFile("batdb", "tmp", context.getCacheDir());
            SQLiteDatabase db = SQLiteDatabase.openDatabase(originalFile.getAbsolutePath(), "", null, SQLiteDatabase.OPEN_READWRITE);
            db.execSQL(String.format("ATTACH DATABASE '%s' AS encrypted KEY '%s';", newFile.getAbsolutePath(), password));
            db.beginTransaction();
            db.rawExecSQL("SELECT sqlcipher_export('encrypted')");
            db.setTransactionSuccessful();
            db.endTransaction();
            db.execSQL("DETACH DATABASE encrypted;");
            int version = db.getVersion();
            db.close();
            db = SQLiteDatabase.openDatabase(newFile.getAbsolutePath(), password, null, SQLiteDatabase.OPEN_READWRITE);
            db.setVersion(version);
            db.close();
            originalFile.delete();
            newFile.renameTo(targetFile);
        }
    }

老数据加密时机:

首先看下 CustomOpenHelper 中各个方法在不同情况的执行情况:

安装应用后首次打开,数据库执行:构造方法、onCreate() 、onOpen()

非首次打开:构造方法、onOpen()

数据库版本更新:构造方法、onUpgrade()、onOpen()

根据方法调用情况,可以得知在构造方法或 onOpen() 中均可进行老数据的加密操作。

工程地址

参考文章:

sqlcipher_export 命令官方文档

sqlcipher 的 issues

SQLite 命令

已有数据库执行加密耗时久的问题