最近我们做的移动im打算将数据库加密,我们的数据库是对数据库的简单封装 ,调研了一些开源数据库加密工具,觉得sqlcipher用户会多一点,而且开源。所以打算就用它了
sqlcipher的使用可以参考下这两篇文章:
http://www.jianshu.com/p/3baf311f8c8c
https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_default_kdf_iter
因为之前我们发的版本是没有加密的,所以面临的问题是怎么读取之前未加密的数据库或者读取之前怎么加密。
我们选择了后者,为了将用户数据最大程度的保留(im 一些本地消息的状态)
加密的方法用的是sqlcipher_export(),具体在android中的用法如下
public static void encrypt(Context context, String dbName,
String passphrase) throws IOException {
File originalFile = context.getDatabasePath(dbName);
if (originalFile.exists()) {
File newFile =
File.createTempFile("sqlcipherutils", "tmp",
context.getCacheDir());
SQLiteDatabase db =
SQLiteDatabase.openDatabase(originalFile.getAbsolutePath(),
"", null,
SQLiteDatabase.OPEN_READWRITE);
db.rawExecSQL(String.format("ATTACH DATABASE '%s' AS encrypted KEY '%s';",
newFile.getAbsolutePath(), passphrase));
db.rawExecSQL("SELECT sqlcipher_export('encrypted')");
db.rawExecSQL("DETACH DATABASE encrypted;");
int version = db.getVersion();
db.close();
db =
SQLiteDatabase.openDatabase(newFile.getAbsolutePath(),
passphrase, null,
SQLiteDatabase.OPEN_READWRITE);
db.setVersion(version);
db.close();
originalFile.delete();
newFile.renameTo(originalFile);
}
}
但是加密的时机,并没有太多的资料,本来考虑的是通过数据库onupdate的时候进行加密,但是验证后发现,根本在读取sqlite_master(备注)表的时候就已经报错,说明在检查升级前就已经在读取数据库数据了
报错如下
file is encrypted or is not a database: , while compiling: select count(*) from sqlite_master;
net.sqlcipher.database.SQLiteException: file is encrypted or is not a database: , while compiling: select count(*) from sqlite_master;
at net.sqlcipher.database.SQLiteCompiledSql.native_compile(Native Method)
at net.sqlcipher.database.SQLiteCompiledSql.compile(SQLiteCompiledSql.java:91)
at net.sqlcipher.database.SQLiteCompiledSql.(SQLiteCompiledSql.java:64)
at net.sqlcipher.database.SQLiteProgram.(SQLiteProgram.java:89)
at net.sqlcipher.database.SQLiteQuery.(SQLiteQuery.java:48)
at net.sqlcipher.database.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:60)
at net.sqlcipher.database.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1867)
at net.sqlcipher.database.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1785)
at net.sqlcipher.database.SQLiteDatabase.keyDatabase(SQLiteDatabase.java:2486)
at net.sqlcipher.database.SQLiteDatabase.openDatabaseInternal(SQLiteDatabase.java:2415)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1149)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1041)
at net.sqlcipher.database.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:249)
at net.sqlcipher.database.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:214)
所以我采用的方案是在getreadabledatabase(“passphase”)||getwriteabledatabase("passphase")的时候进行加密
try {
return this.tempDB != null ? this.tempDB : (readable ? this.getReadableDatabase("test12") : this.getWritableDatabase("test12"));
} catch (SQLiteException e) {
String message = e.getMessage();
if (message.contains("encrypt") && message.contains("sqlite_master")) {
try {
encrypt(context , dbName , "test12");
return (readable ? this.getReadableDatabase("test12") : this.getWritableDatabase("test12"));
} catch (IOException e1) {
e1.printStackTrace();
return null;
}
}
return null;
备注:
关于sqlitemaster:是一张B+树用于查询