如何降級(jí)加密的Sqlite數(shù)據(jù)庫(kù)文件

前情提要

之前升級(jí)Electron框架至5.x版本時(shí),因編譯報(bào)sqlite3相關(guān)錯(cuò),加上Sqlcipher官網(wǎng)上聲稱(chēng)4.x版本在性能和安全性上有了很大提升。因此,將項(xiàng)目的sqlite3庫(kù)做了基于4.x Sqlcipher的重編譯。重編譯后的庫(kù),只需要在連接原數(shù)據(jù)庫(kù)文件后,運(yùn)行PRAGMA cipher_migrate語(yǔ)句,就能將數(shù)據(jù)庫(kù)文件升級(jí)為具有4.x特性(具體哪些特性沒(méi)在意...)。具體可參考作者的升級(jí)介紹:Upgrading to SQLCipher 4

升級(jí)后,基于Electron 5.x的項(xiàng)目確實(shí)能運(yùn)行了。But!舊的sqlite庫(kù)無(wú)法打開(kāi)升級(jí)后的數(shù)據(jù)庫(kù)文件了。一時(shí)半會(huì)也沒(méi)那么多時(shí)間去解決Electron升級(jí)后的各種兼容問(wèn)題,只能想辦法怎么把數(shù)據(jù)庫(kù)恢復(fù)了。

如何降級(jí)加密數(shù)據(jù)庫(kù)文件?

需求

將數(shù)據(jù)庫(kù)文件降級(jí),支持原sqlcipher(3.x)打開(kāi)。

問(wèn)題

Sqlcipher提供了數(shù)據(jù)庫(kù)升級(jí)語(yǔ)句PRAGMA cipher_migrate,但沒(méi)有降級(jí)的(倒是有個(gè)高版本庫(kù)以低版本特性運(yùn)行的語(yǔ)句:PRAGMA cipher_compatibility = 3;)。這里還有個(gè)老哥提過(guò)類(lèi)似需求:Downgrade from version 4.2.0 to 3.5.9,然而作者明確回復(fù)沒(méi)有直接降級(jí)的方法。

分析

既然沒(méi)有現(xiàn)成的Downgrade方法,那只能另辟蹊徑了。在查看了官網(wǎng)的各種文檔和各種嘗試后,總結(jié)下可行的操作步驟:

  1. 使用升級(jí)后的Sqlcipher庫(kù),將數(shù)據(jù)庫(kù)文件解密轉(zhuǎn)化為非加密的純文本數(shù)據(jù)庫(kù)文件(plaintext db);
  2. 使用原Sqlcipher庫(kù),在plaintext db基礎(chǔ)上復(fù)制一份加密庫(kù)文件,然后進(jìn)行數(shù)據(jù)遷移。

主要用到的是數(shù)據(jù)遷移相關(guān)的關(guān)鍵字ATTACHsqlcipher_export()函數(shù)。

實(shí)現(xiàn)降級(jí)的過(guò)程這樣看下來(lái)并不復(fù)雜,無(wú)非就是加密庫(kù)和非加密庫(kù)之間的來(lái)回?cái)?shù)據(jù)遷移。關(guān)鍵在于區(qū)分好前后使用的Sqlcipher版本。

降級(jí)的具體操作流程

  1. 將4.x版本的數(shù)據(jù)庫(kù)文件解密生成純文本數(shù)據(jù)庫(kù)文件

    這一步需要用到4.x版本的Sqlcipher,之前重編譯Sqlite時(shí)有在本地全局安裝Sqlcipher。因此,這一步,直接在終端操作:

    $ ./sqlcipher encrypted.db
    sqlite> PRAGMA key = 'testkey';
    sqlite> ATTACH DATABASE 'plaintext.db' AS plaintext KEY '';  -- empty key will disable encryption
    sqlite> SELECT sqlcipher_export('plaintext');
    sqlite> DETACH DATABASE plaintext;
    

    成功執(zhí)行后,得到?jīng)]有加密的純文本數(shù)據(jù)庫(kù)文件plaintext

  2. 使用舊版本Sqlcipher對(duì)純文本數(shù)據(jù)庫(kù)文件重加密

    本地沒(méi)有舊版本的Sqlcipher,使用的是項(xiàng)目里已經(jīng)編譯好的加密版Sqlite。新建一個(gè)JS文件,執(zhí)行以下code:

    const sqlcipher = require('cross-sqlcipher').verbose();
    const sqlite3 = require('sqlite3').verbose();
    const dbPath = path.resolve('./plaintext.db');
    const plainDb = new sqlcipher.Database(dbPath, (err) => {
      if (!err) {
        console.log('open plain db success');
        plainDb.serialize(() => {
          plainDb.run('ATTACH DATABASE \'encrypted.db\' AS encrypted KEY \'testkey\'', (err, res) => {
               console.log('attach finish');
             }).run('SELECT sqlcipher_export(\'encrypted\')', (err, res) => {
                console.log('sqlcipher_export finish');
             }).run('DETACH DATABASE encrypted');
        });    
     }
    });
    

    執(zhí)行完畢后,生成的encrypted.db即可以被原項(xiàng)目的Sqlite庫(kù)正常打開(kāi)。

引申:數(shù)據(jù)庫(kù)異常 - SQLITE_CORRUPT

// log
{ Error: SQLITE_CORRUPT: database disk image is malformed errno: 11, code: 'SQLITE_CORRUPT' }

參考:ERROR: SQLite database is malformed – SOLVED

之前升級(jí)了本地的sqlcipher版本,無(wú)法打開(kāi)用戶(hù)的數(shù)據(jù)庫(kù)文件,重新下載項(xiàng)目中的版本3.4.1,復(fù)制到目錄(macos)/usr/local/Cellar/sqlcipher , 使用bin目錄下sqlcipher程序打開(kāi)用戶(hù)數(shù)據(jù)庫(kù)文件。

解密后,導(dǎo)出完整的數(shù)據(jù)庫(kù)語(yǔ)句:

./bin/sqlcipher _data.db
PRAGMA key = 'yourkey';
.mode insert;
.output data_export.sql;
.dump
.exit

完成以上指令后,數(shù)據(jù)庫(kù)的所有數(shù)據(jù)已經(jīng)導(dǎo)出到sql文件中,通過(guò)該文件重新生成db文件。

// 備份
mv _data.db _data.db.original
sqlite3 _data.db < data_export.sql

校驗(yàn)新生成的數(shù)據(jù)庫(kù)是否能正常打開(kāi)。參考上面Sqlcipher加密純文本數(shù)據(jù)庫(kù)文件的步驟,完成數(shù)據(jù)庫(kù)加密。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容