android db文件加密,Android从非加密数据库迁移到加密数据库

title: Android从非加密数据库迁移到加密数据库

date: 2019-12-26 13:26:53

categories:

- Android

tags:

- SQLITE

如果你之前使用的是非加密数据库,想迁移到加密数据库并保留原来的数据,你需要使用 SQL 函数 sqlcipher_export() 进行迁移。

方案一:WCDB迁移

WCDB 对 sqlcipher_export() 函数做了扩展,原本只接受一个参数为导出到哪个 ATTACHED DB, 现在可以接受第二个参数指定从哪个 DB 导出。因此可以反过来实现导入:

ATTACH 'old_database' AS old;

SELECT sqlcipher_export('main', 'old'); -- 从 'old' 导入到 'main'

DETACH old;

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" //加密必要

关键代码如下:

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

}

}

注意事项:

1.WCDB默认加密后的db文件的pagesize为4096字节,为了与官方SQLCipher创建的数据库兼容库中,必须使用页面大小指定SQLiteCipherSpec1024字节。否则Android代码打开数据库时将提示:

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

2.在进行任何操作之前需要先使用pragma key=...来解密数据库,否则可能会报错“Error: file is encrypted or is not a database”,这里网上也有很多人跟我一样遇到。

3.wcdb使用了sqlcipher来加密的,在加解密的时候必须使用一致的版本,比如我们使用sqlcipher3.x加密的,那么在解密的时候也必须使用3.x版本,否则就会解密失败。

原因:https://github.com/Tencent/wcdb/search?q=64000&unscoped_q=64000

参考

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

测试代码

测试软件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值