本地數(shù)據(jù)加密
由于項目涉及到一些用戶隱私數(shù)據(jù)的存儲,所以需要對保存在客戶端本地的數(shù)據(jù)進行加密葵硕,以防止用戶隱私數(shù)據(jù)在設備被root的情況下出現(xiàn)泄漏。目前android的本地數(shù)據(jù)存儲基本分為file季惩,sharepreference和database御蒲,所以對數(shù)據(jù)的加密操作分為了兩種:文件加密和文件內(nèi)的數(shù)據(jù)加密。文件加密就是在打開該文件的時候需要獲得正確的加密秘鑰才能從該文件中讀取數(shù)據(jù)或者寫入數(shù)據(jù)到該文件中垦梆,這種方式相對簡單匹颤。文件內(nèi)數(shù)據(jù)加密就是打開文件時不需要解密,但是從文件中讀取出來的數(shù)據(jù)是加密過的密文托猩,需要對其進行解密才能識別和使用印蓖,同樣在寫入數(shù)據(jù)到文件的時候,也需要先將數(shù)據(jù)進行加密然后再寫入到文件京腥,這種方式相比第一種復雜一些赦肃,但是安全性更高,對數(shù)據(jù)保護的粒度更細公浪。file和sharepreference使用上述兩種方式實現(xiàn)的成本差異并不明顯他宛,database使用文件內(nèi)數(shù)據(jù)比如列字段加密相比文件加密要更加復雜,所以database通常使用文件加密的方式來實現(xiàn)欠气,比如有名的sqlcipher就是采用的這種方式厅各,今天我們將要提到的wcdb也是采用的這種方式來保護數(shù)據(jù)的。
WCDB
wcdb是微信團隊貢獻的一個開源項目预柒,是一個高效易用的數(shù)據(jù)庫框架队塘,并且支持多個平臺。關于wcdb的更多介紹以及如何集成和使用WCDB請參考Tencent/wcdb宜鸯,這里不再贅述憔古。下面主要介紹一下我在將WCDB集成到原有項目(Android客戶端)的過程中遇到的一些問題以及解決方案。因為我也是在使用WCDB的過程中不斷查找資料淋袖,發(fā)現(xiàn)了一些別人沒有遇到或者別人遇到了自己也遇到了但是沒有清楚答案的問題鸿市,所以才想記錄下來,以作備忘适贸。
混淆
通常我們在引入一些知名的第三方庫的時候灸芳,都需要在proguard中加入一些規(guī)則來屏蔽對該庫的混淆,因為混淆可能會導致該庫的部分功能異常拜姿,比如glide的項目介紹上就有如下說明:
但是我們在wcdb的項目上沒有看到關于混淆這一塊的介紹烙样,剛開始我以為不需要,后來打了一個release包后發(fā)現(xiàn)wcdb運行報錯蕊肥,才知道這里還是有必要加一下的谒获。內(nèi)容如下:
-keep class com.tencent.wcdb.** {*;}
關于在proguard中如何加入第三方庫的放混淆配置蛤肌,可以使用以下方法。在android studio的project視圖下的External Libraries中找到對應的庫名字批狱,比如wcdb裸准,然后就可以看到這個庫的完整包路徑了。如下圖:
加密已有數(shù)據(jù)
由于我的項目以前是使用android原生的sqlite儲存數(shù)據(jù)赔硫,現(xiàn)在要遷移到wcdb上炒俱,就必須考慮到版本兼容的問題,當老版本升級到新版本后確保老版本上保存在本地的數(shù)據(jù)能無縫遷移到新版本上面來爪膊。那么對于已有數(shù)據(jù)的加密权悟,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')");
然后發(fā)現(xiàn)怎么運行都會報異常。針對這個問題在項目里面還有一個issue:集成WCDB后希望能實現(xiàn)解密數(shù)據(jù)庫 #36耘成。我也遇到了榔昔,后面找了好久才發(fā)現(xiàn)是這里的問題,原因是wcdb的execSQL不支持select瘪菌,但是原生的sqlite以及sqlcipher都是支持撒会,所以在第一次用的時候就會覺得很奇怪,這條語句語法看上去完全沒問題控嗜,運行就是行不通茧彤。
數(shù)據(jù)解密
有時為了方便定位問題,需要查看database的數(shù)據(jù)疆栏,如果是debug包可以直接聯(lián)調(diào)查看,但如果是release包那么就必須手動導出db文件然后進行解密查看了惫谤。這里有兩種方式解密:
- db文件拷貝到電腦上面壁顶,然后安裝sqlcipher工具進行解密
- 在手機上使用wcdb sdk來解密
電腦端使用sqlcipher解密
首先在電腦端安裝sqlcipher工具(鏈接:https://pan.baidu.com/s/1_yCOoqZJTersQ6KmkZb13w
提取碼:jorr ),這里以windows為例溜歪,下載該工具后進入sqlcipher-3.0.1\bin\目錄下若专,打開命令行工具,輸入以下命令蝴猪,如下圖:
其中123456為加密秘鑰调衰,encrypt.db為加密的數(shù)據(jù)庫文件,這里有幾個地方需要注意:
- wcdb默認加密后的db文件的pagesize為4096自阱,所以這里如果不設置cipher_page_size或者設置的值與你在使用wcdb加密時設置的pagesize一致的話嚎莉,就會報錯,這一點我網(wǎng)上找了很久都沒發(fā)現(xiàn)有人提到沛豌,有的文章上設置的是1024趋箩,但是我試過就是不行,后來找了一個未加密的db文件看了下pagesize的值才發(fā)現(xiàn)不對。
- 在進行任何操作之前需要先使用pragma key=...來解密數(shù)據(jù)庫叫确,否則可能會報錯“Error: file is encrypted or is not a database”跳芳,這里網(wǎng)上也有很多人跟我一樣遇到。
- wcdb使用了sqlcipher來加密的竹勉,在加解密的時候必須使用一致的版本飞盆,比如我們使用sqlcipher3.x加密的,那么在解密的時候也必須使用3.x版本次乓,否則就會解密失敗桨啃。
有時在命令行里面解密和查看數(shù)據(jù)不太方便,我們可以將加密db中的數(shù)據(jù)導出到一個未加密的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文件丧慈。執(zhí)行完上述命令后析命,我們就會在當前目錄下看到一個解密后的plaintext.db文件了,然后使用其他的數(shù)據(jù)庫工具如sqlite expert等就可以正常打開查看里面的數(shù)據(jù)了逃默。
wcdb sdk解密
使用wcdb sdk解密數(shù)據(jù)與之前提到的加密數(shù)據(jù)的過程是相識的鹃愤,這里結(jié)合代碼來詳細說明。
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(getDataBase("encrypt.db"), "123456".getBytes(), null, null);
//將要生成的未加密db文件完域,這里可以根據(jù)自己的需要放在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);
//導出加密數(shù)據(jù)庫到未加密數(shù)據(jù)庫中软吐,sqlcipher_export這個方法有兩個參數(shù),第一個參數(shù)plaintext代表新生成的未加密數(shù)據(jù)庫吟税,第二個參數(shù)main代表已加密的數(shù)據(jù)庫凹耙,也可以不使用第二個參數(shù),那么默認將使用db關聯(lián)的數(shù)據(jù)庫導出到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;");
這樣我們就將一個加密數(shù)據(jù)庫導出到了plaintext.db中肖抱。
總結(jié)
本文算是自己在項目中使用WCDB過程中一些使用心得和問題總結(jié),WCDB在使用這一塊其實還有更多高級的用法异旧,這里并沒有提到意述,后面有時間了再做詳述。