本文是对 DBFlow 的基础使用以及兼容老版本数据实现数据库加密的记录。
背景
为了用户数据安全,需要加密数据库中的数据。加密数据可以实现的方式有:
- 加密特定字段,存入的时候加密字段值,使用的时候解密;
- 使用 sqlcipher 加密整个数据库文件;
现状介绍
App 已经上线,有用户在用,所以要考虑加密之后已有数据的兼容性;
DBFlow 的使用
-
添加 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" -
初始化 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()); -
创建数据表
新建类 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() 中均可进行老数据的加密操作。
参考文章: