wcdb使用筆記

okgays發表於2018-11-16

本地資料加密

由於專案涉及到一些使用者隱私資料的儲存,所以需要對儲存在客戶端本地的資料進行加密,以防止使用者隱私資料在裝置被root的情況下出現洩漏。目前android的本地資料儲存基本分為file,sharepreference和database,所以對資料的加密操作分為了兩種:檔案加密和檔案內的資料加密。檔案加密就是在開啟該檔案的時候需要獲得正確的加密祕鑰才能從該檔案中讀取資料或者寫入資料到該檔案中,這種方式相對簡單。檔案內資料加密就是開啟檔案時不需要解密,但是從檔案中讀取出來的資料是加密過的密文,需要對其進行解密才能識別和使用,同樣在寫入資料到檔案的時候,也需要先將資料進行加密然後再寫入到檔案,這種方式相比第一種複雜一些,但是安全性更高,對資料保護的粒度更細。file和sharepreference使用上述兩種方式實現的成本差異並不明顯,database使用檔案內資料比如列欄位加密相比檔案加密要更加複雜,所以database通常使用檔案加密的方式來實現,比如有名的sqlcipher就是採用的這種方式,今天我們將要提到的wcdb也是採用的這種方式來保護資料的。

WCDB

wcdb是微信團隊貢獻的一個開源專案,是一個高效易用的資料庫框架,並且支援多個平臺。關於wcdb的更多介紹以及如何整合和使用WCDB請參考Tencent/wcdb,這裡不再贅述。下面主要介紹一下我在將WCDB整合到原有專案(Android客戶端)的過程中遇到的一些問題以及解決方案。因為我也是在使用WCDB的過程中不斷查詢資料,發現了一些別人沒有遇到或者別人遇到了自己也遇到了但是沒有清楚答案的問題,所以才想記錄下來,以作備忘。

混淆

通常我們在引入一些知名的第三方庫的時候,都需要在proguard中加入一些規則來遮蔽對該庫的混淆,因為混淆可能會導致該庫的部分功能異常,比如glide的專案介紹上就有如下說明:

wcdb使用筆記
但是我們在wcdb的專案上沒有看到關於混淆這一塊的介紹,剛開始我以為不需要,後來打了一個release包後發現wcdb執行報錯,才知道這裡還是有必要加一下的。內容如下:

-keep class com.tencent.wcdb.** {*;}
複製程式碼

關於在proguard中如何加入第三方庫的放混淆配置,可以使用以下方法。在android studio的project檢視下的External Libraries中找到對應的庫名字,比如wcdb,然後就可以看到這個庫的完整包路徑了。如下圖:

wcdb使用筆記

加密已有資料

由於我的專案以前是使用android原生的sqlite儲存資料,現在要遷移到wcdb上,就必須考慮到版本相容的問題,當老版本升級到新版本後確保老版本上儲存在本地的資料能無縫遷移到新版本上面來。那麼對於已有資料的加密,wcdb的專案裡面也提供了一個例子,我將核心程式碼貼出來:

File oldDbFile = mContext.getDatabasePath(OLD_DATABASE_NAME);
if (oldDbFile.exists()) {
    // SQLiteOpenHelper begins a transaction before calling onCreate().
    // We have to end the transaction before we can attach a new database.
    db.endTransaction();

    // Attach old database to the newly created, encrypted database.
    String sql = String.format("ATTACH DATABASE %s AS old KEY '';",
            DatabaseUtils.sqlEscapeString(oldDbFile.getPath()));
    db.execSQL(sql);

    // Export old database.
    db.beginTransaction();
    DatabaseUtils.stringForQuery(db, "SELECT sqlcipher_export('main', 'old');", null);
    db.setTransactionSuccessful();
    db.endTransaction();

    // Get old database version for later upgrading.
    int oldVersion = (int) DatabaseUtils.longForQuery(db, "PRAGMA old.user_version;", null);

    // Detach old database and enter a new transaction.
    db.execSQL("DETACH DATABASE old;");

    // Old database can be deleted now.
    oldDbFile.delete();
}
複製程式碼

這裡很多第一次使用wcdb的童鞋,如果沒有細看這個例子中的程式碼會比較容易犯一個錯誤,就是將

DatabaseUtils.stringForQuery(db, "SELECT sqlcipher_export('main', 'old');", null);
複製程式碼

不小心寫成了

db.execSQL("SELECT sqlcipher_export('encrypted')");
複製程式碼

然後發現怎麼執行都會報異常。針對這個問題在專案裡面還有一個issue:整合WCDB後希望能實現解密資料庫 #36。我也遇到了,後面找了好久才發現是這裡的問題,原因是wcdb的execSQL不支援select,但是原生的sqlite以及sqlcipher都是支援,所以在第一次用的時候就會覺得很奇怪,這條語句語法看上去完全沒問題,執行就是行不通。

資料解密

有時為了方便定位問題,需要檢視database的資料,如果是debug包可以直接聯調檢視,但如果是release包那麼就必須手動匯出db檔案然後進行解密檢視了。這裡有兩種方式解密:

  1. db檔案拷貝到電腦上面,然後安裝sqlcipher工具進行解密
  2. 在手機上使用wcdb sdk來解密

電腦端使用sqlcipher解密

首先在電腦端安裝sqlcipher工具(連結:pan.baidu.com/s/1_yCOoqZJ… 提取碼:jorr ),這裡以windows為例,下載該工具後進入sqlcipher-3.0.1\bin\目錄下,開啟命令列工具,輸入以下命令,如下圖:

wcdb使用筆記

wcdb使用筆記
其中123456為加密祕鑰,encrypt.db為加密的資料庫檔案,這裡有幾個地方需要注意:

  1. wcdb預設加密後的db檔案的pagesize為4096,所以這裡如果不設定cipher_page_size或者設定的值與你在使用wcdb加密時設定的pagesize一致的話,就會報錯,這一點我網上找了很久都沒發現有人提到,有的文章上設定的是1024,但是我試過就是不行,後來找了一個未加密的db檔案看了下pagesize的值才發現不對。
  2. 在進行任何操作之前需要先使用pragma key=...來解密資料庫,否則可能會報錯“Error: file is encrypted or is not a database”,這裡網上也有很多人跟我一樣遇到。
  3. wcdb使用了sqlcipher來加密的,在加解密的時候必須使用一致的版本,比如我們使用sqlcipher3.x加密的,那麼在解密的時候也必須使用3.x版本,否則就會解密失敗。

有時在命令列裡面解密和檢視資料不太方便,我們可以將加密db中的資料匯出到一個未加密的db中,首先我們在命令列工具中使用sqlcipher開啟encrypt.db檔案,然後輸入如下命令:

pragma key='123456';
pragma cipher_page_size=4096;
attach database 'plaintext.db' as plaintext key '';
select sqlcipher_export('plaintext');
detach database plaintext;
複製程式碼

其中123456為加密祕鑰,palintext.db為解密後的db檔案。執行完上述命令後,我們就會在當前目錄下看到一個解密後的plaintext.db檔案了,然後使用其他的資料庫工具如sqlite expert等就可以正常開啟檢視裡面的資料了。

wcdb sdk解密

使用wcdb sdk解密資料與之前提到的加密資料的過程是相識的,這裡結合程式碼來詳細說明。

SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(getDataBase("encrypt.db"), "123456".getBytes(), null, null);
//將要生成的未加密db檔案,這裡可以根據自己的需要放在sd目錄中方便匯出檢視
File plainDbFile = mContext.getDatabasePath("plaintext.db");
// Attach database 
String sql = String.format("ATTACH DATABASE %s AS plaintext KEY ';",
        DatabaseUtils.sqlEscapeString(plainDbFile.getPath()));
db.execSQL(sql);

//匯出加密資料庫到未加密資料庫中,sqlcipher_export這個方法有兩個引數,第一個引數plaintext代表新生成的未加密資料庫,第二個引數main代表已加密的資料庫,也可以不使用第二個引數,那麼預設將使用db關聯的資料庫匯出到plaintext中。詳細使用可以檢視wcdb開源專案的android demo
db.beginTransaction();
DatabaseUtils.stringForQuery(db, "SELECT sqlcipher_export('plaintext', 'main');", null);
db.setTransactionSuccessful();
db.endTransaction();

// Detach plaintext database
db.execSQL("DETACH DATABASE plaintext;");
複製程式碼

這樣我們就將一個加密資料庫匯出到了plaintext.db中。

總結

本文算是自己在專案中使用WCDB過程中一些使用心得和問題總結,WCDB在使用這一塊其實還有更多高階的用法,這裡並沒有提到,後面有時間了再做詳述。

相關文章