1. 程式人生 > >sqlcipher加密已有資料庫及其時機

sqlcipher加密已有資料庫及其時機

  最近我們做的移動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.<init>(SQLiteCompiledSql.java:64)
                                                             at net.sqlcipher.database.SQLiteProgram.<init>(SQLiteProgram.java:89)
                                                             at net.sqlcipher.database.SQLiteQuery.<init>(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+樹用於查詢