Android從非加密數(shù)據(jù)庫遷移到加密數(shù)據(jù)庫


title: Android從非加密數(shù)據(jù)庫遷移到加密數(shù)據(jù)庫
date: 2019-12-26 13:26:53
categories:
- Android
tags:
- SQLITE


如果你之前使用的是非加密數(shù)據(jù)庫总放,想遷移到加密數(shù)據(jù)庫并保留原來的數(shù)據(jù)滤港,你需要使用 SQL 函數(shù) sqlcipher_export() 進(jìn)行遷移。

方案一:WCDB遷移

WCDB 對 sqlcipher_export() 函數(shù)做了擴(kuò)展褪猛,原本只接受一個(gè)參數(shù)為導(dǎo)出到哪個(gè) ATTACHED DB, 現(xiàn)在可以接受第二個(gè)參數(shù)指定從哪個(gè) DB 導(dǎo)出吃环。因此可以反過來實(shí)現(xiàn)導(dǎo)入:

ATTACH 'old_database' AS old;
SELECT sqlcipher_export('main', 'old');   -- 從 'old' 導(dǎo)入到 'main'
DETACH old;

詳情請見 sample-encryptdb[https://github.com/Tencent/wcdb/tree/master/android/samples/sample-encryptdb] 示例镶柱,它示范了如何使用 SQLiteOpenHelper 實(shí)現(xiàn)數(shù)據(jù)從非加密往加密遷移和 Schema 升級。

import android.content.Context;
import android.util.Log;

import com.tencent.wcdb.DatabaseUtils;
import com.tencent.wcdb.database.SQLiteChangeListener;
import com.tencent.wcdb.database.SQLiteDatabase;
import com.tencent.wcdb.database.SQLiteOpenHelper;
import com.tencent.wcdb.repair.RepairKit;

import java.io.File;


public class EncryptedDBHelper extends SQLiteOpenHelper {

    private static final String TAG = "EncryptedDBHelper";

    private static final String DATABASE_NAME = "encrypted.db";
    private static final String OLD_DATABASE_NAME = "plain-text.db";
    private static final int DATABASE_VERSION = 2;

    private Context mContext;
    private String mPassphrase;

    // The test database is taken from SQLCipher test-suit.
    //
    // To be compatible with databases created by the official SQLCipher
    // library, a SQLiteCipherSpec must be specified with page size of
    // 1024 bytes.
    static final SQLiteCipherSpec CIPHER_SPEC = new SQLiteCipherSpec()
            .setPageSize(1024);
    public EncryptedDBHelper(Context context, String passphrase) {

        // Call "encrypted" version of the superclass constructor.
        super(context, DATABASE_NAME, passphrase.getBytes(), CIPHER_SPEC , null, DATABASE_VERSION,
                null);

        // Save context object for later use.
        mContext = context;
        mPassphrase = passphrase;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // Check whether old plain-text database exists, if so, export it
        // to the new, encrypted one.
        File oldDbFile = mContext.getDatabasePath(OLD_DATABASE_NAME);
        if (oldDbFile.exists()) {

            Log.i(TAG, "Migrating plain-text database to encrypted one.");

            // 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();

            // Before further actions, restore the transaction.
            db.beginTransaction();

            // Check if we need to upgrade the schema.
            if (oldVersion > DATABASE_VERSION) {
                onDowngrade(db, oldVersion, DATABASE_VERSION);
            } else if (oldVersion < DATABASE_VERSION) {
                onUpgrade(db, oldVersion, DATABASE_VERSION);
            }
        } else {
            Log.i(TAG, "Creating new encrypted database.");

            // Do the real initialization if the old database is absent.
            db.execSQL("CREATE TABLE message (content TEXT, "
                    + "sender TEXT);");
        }

        // OPTIONAL: backup master info for corruption recovery.
        RepairKit.MasterInfo.save(db, db.getPath() + "-mbak", mPassphrase.getBytes());
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        Log.i(TAG, String.format("Upgrading database from version %d to version %d.",
                oldVersion, newVersion));

        // Add new column to message table on database upgrade.
        db.execSQL("ALTER TABLE message ADD COLUMN sender TEXT;");

        // OPTIONAL: backup master info for corruption recovery.
        RepairKit.MasterInfo.save(db, db.getPath() + "-mbak", mPassphrase.getBytes());
    }

    @Override
    public void onConfigure(SQLiteDatabase db) {
        db.setAsyncCheckpointEnabled(true);
        db.setChangeListener(new SQLiteChangeListener() {

            private StringBuilder mSB = new StringBuilder();
            private void printIds(String prefix, long[] ids) {
                mSB.append(prefix).append(": ");
                for (long id : ids) {
                    mSB.append(id).append(", ");
                }
                Log.i(TAG, mSB.toString());
                mSB.setLength(0);
            }

            @Override
            public void onChange(SQLiteDatabase db, String dbName, String table,
                    long[] insertIds, long[] updateIds, long[] deleteIds) {
                Log.i(TAG, "onChange called: dbName = " + dbName + ", table = " + table);
                printIds("INSERT", insertIds);
                printIds("UPDATE", updateIds);
                printIds("DELETE", deleteIds);
            }
        }, true);
    }
}

方案二:SQLCipher 遷移

從 SQLCipher Android 遷移
gradle加入

implementation "net.zetetic:android-database-sqlcipher:3.5.9@aar" //加密必要

關(guān)鍵代碼如下:

private static void convertNormalToSQLCipheredDB(Context context,String startingFileName, String endingFileName, String filePassword)
 throws IOException {
  File mStartingFile = context.getDatabasePath(startingFileName);
  if (!mStartingFile.exists()) {
   return;
  }
  File mEndingFile = context.getDatabasePath(endingFileName);
  mEndingFile.delete();
  SQLiteDatabase database = null;
  try {
   database = SQLiteDatabase.openOrCreateDatabase(MainApp.mainDBPath,
   "", null);
   database.rawExecSQL(String.format(
   "ATTACH DATABASE '%s' AS encrypted KEY '%s'",
   mEndingFile.getAbsolutePath(), filePassword));
   database.rawExecSQL("select sqlcipher_export('encrypted')");
   database.rawExecSQL("DETACH DATABASE encrypted");
   database.close();
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (database.isOpen())
    database.close();
   mStartingFile.delete();
  }
 }

詳情請見https://codeday.me/bug/20191030/1964560.html

注意事項(xiàng):

1.WCDB默認(rèn)加密后的db文件的pagesize為4096字節(jié),為了與官方SQLCipher創(chuàng)建的數(shù)據(jù)庫兼容庫中模叙,必須使用頁面大小指定SQLiteCipherSpec1024字節(jié)。否則Android代碼打開數(shù)據(jù)庫時(shí)將提示:

net.sqlcipher.database.SQLiteException: file is encrypted or is not a database

2.在進(jìn)行任何操作之前需要先使用pragma key=...來解密數(shù)據(jù)庫鞋屈,否則可能會報(bào)錯(cuò)“Error: file is encrypted or is not a database”范咨,這里網(wǎng)上也有很多人跟我一樣遇到。
3.wcdb使用了sqlcipher來加密的厂庇,在加解密的時(shí)候必須使用一致的版本渠啊,比如我們使用sqlcipher3.x加密的,那么在解密的時(shí)候也必須使用3.x版本权旷,否則就會解密失敗替蛉。
原因:https://github.com/Tencent/wcdb/search?q=64000&unscoped_q=64000

參考

1.wcdb使用筆記
http://www.reibang.com/p/8fab9eba909d
2.SQlite數(shù)據(jù)庫的加密與解密
http://www.reibang.com/p/0b2376f3d579
3.解決sqlcipher從3.5.9升級到4.0.1引起的崩潰問題

Caused by: net.sqlcipher.database.SQLiteException: file is not a database: , while compiling: select count(*) from sqlite_master;

http://www.reibang.com/p/9579f9cef85b

測試代碼

https://github.com/CentForever/TestDBFlow.git

測試軟件

https://sqlitestudio.pl/index.rvt?act=download

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拄氯,隨后出現(xiàn)的幾起案子躲查,更是在濱河造成了極大的恐慌,老刑警劉巖译柏,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镣煮,死亡現(xiàn)場離奇詭異,居然都是意外死亡鄙麦,警方通過查閱死者的電腦和手機(jī)典唇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胯府,“玉大人介衔,你說我怎么就攤上這事÷钜颍” “怎么了炎咖?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寒波。 經(jīng)常有香客問我塘装,道長,這世上最難降的妖魔是什么影所? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任蹦肴,我火速辦了婚禮,結(jié)果婚禮上猴娩,老公的妹妹穿的比我還像新娘阴幌。我一直安慰自己勺阐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布矛双。 她就那樣靜靜地躺著渊抽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪议忽。 梳的紋絲不亂的頭發(fā)上懒闷,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機(jī)與錄音栈幸,去河邊找鬼愤估。 笑死,一個(gè)胖子當(dāng)著我的面吹牛速址,可吹牛的內(nèi)容都是我干的玩焰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼芍锚,長吁一口氣:“原來是場噩夢啊……” “哼昔园!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起并炮,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤默刚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后逃魄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羡棵,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年嗅钻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了皂冰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡养篓,死狀恐怖秃流,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柳弄,我是刑警寧澤舶胀,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站碧注,受9級特大地震影響嚣伐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜萍丐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一轩端、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逝变,春花似錦基茵、人聲如沸郑气。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绿渣。三九已至,卻和暖如春根灯,著一層夾襖步出監(jiān)牢的瞬間径缅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工烙肺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纳猪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓茬高,卻偏偏與公主長得像,于是被迫代替她去往敵國和親假抄。 傳聞我的和親對象是個(gè)殘疾皇子怎栽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

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