如何使用 sqlcipher 加密现有的 Room 数据库?

How can I encrypt the existing Room database with sqlcipher?

一段时间以来,我一直在寻找 Room 数据库安全的解决方案,并熟悉了 sqlcipher,但找不到合适的解决方案来使用它。

终于到了地址 Link 我到了,在这个网站上的培训的帮助下,我能够做一些事情。

我是这样写代码的:

import android.content.Context;
import android.content.SharedPreferences;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;

import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SupportFactory;

import java.io.IOException;

import static ir.sharikapp.sharik.PopularMethods.randomPassword;
import static ir.sharikapp.sharik.PreferenceManager.mEditor;
import static ir.sharikapp.sharik.PreferenceManager.mPrefs;

@Database(version = 3, exportSchema = false, entities = {Transaction.class, Partners.class, Subscription.class})
public abstract class AppDatabase extends RoomDatabase {
    private static AppDatabase appDatabase;
    static String pass = null;
    static byte[] passphrase = null;
    static SupportFactory factory = null;
    static SharedPreferences sharedPreferences = null;
    static SharedPreferences.Editor sharedPreferencesEditor = null;


    public static AppDatabase getAppDatabase(Context context) {
        if (factory == null) {
            if (sharedPreferences == null) {
                PreferenceManager.getInstance(context);
                sharedPreferences = mPrefs;
                sharedPreferencesEditor = mEditor;
            }

            if (!sharedPreferences.contains("dbEncrypted")) {
                sharedPreferencesEditor.putString("dbPass", randomPassword(50));
                sharedPreferencesEditor.putString("dbEncrypted", "yes");
                sharedPreferencesEditor.apply();
            }
            pass = sharedPreferences.getString("dbPass", null);
            passphrase = SQLiteDatabase.getBytes(pass.toCharArray());
            factory = new SupportFactory(passphrase);
        }

        if (appDatabase == null)
            appDatabase = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "dp_app")
                    .addMigrations(MIGRATION_2_3)
                    .allowMainThreadQueries()
                    .openHelperFactory(factory)
                    .build();
        return appDatabase;
    }

    // Migration from 2 to 3
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database = SQLiteDatabase.openOrCreateDatabase("dp_app.db","", null);
            database.query(
                    "ATTACH DATABASE '${encryptedDbPath}' AS encrypted KEY '${passphrase}'");
            database.query("select sqlcipher_export('encrypted')");
            database.query("DETACH DATABASE encrypted");
            try {
                database.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
    
        public abstract SubscriptionDao getSubscriptionDao();
    
        public abstract TransactionDao getTransactionDao();
    
        public abstract PartnersDao getPartnersDao();
    
    }

pass = sharedPreferences.getString("dbPass", null);

这行代码捕获了软件第一次使用时存储在sharedPreferences中的密码运行。


迁移内部和队列中

database.query("ATTACH DATABASE '${encryptedDbPath}' AS encrypted KEY '${passphrase}'");

这可能是个问题。


另外,看到上面Link中提到的例子,迁移方法是这样写的

database = SQLiteDatabase.openOrCreateDatabase("clearDatabase.db","", null);
database.rawExecSQL(
   "ATTACH DATABASE '${encryptedDbPath}' AS encrypted KEY '${passphrase}'");
database.rawExecSQL("select sqlcipher_export('encrypted')");
database.rawExecSQL("DETACH DATABASE encrypted");
database.close();

但是我无法使用database.rawExecSQL,因为我在写这段代码的时候,androidstudio在KEY下划线是错误的;这就是我使用 database.query 的原因。这会是我的错吗?


当我 运行 程序时,我得到一个错误,我把它放在它的 stackTrace 下。

  net.sqlcipher.database.SQLiteException: file is not a database: , while compiling: select count(*) from sqlite_master;
        at net.sqlcipher.database.SQLiteCompiledSql.native_compile(Native Method)
        at net.sqlcipher.database.SQLiteCompiledSql.compile(SQLiteCompiledSql.java:89)
        at net.sqlcipher.database.SQLiteCompiledSql.<init>(SQLiteCompiledSql.java:62)
        at net.sqlcipher.database.SQLiteProgram.<init>(SQLiteProgram.java:91)
        at net.sqlcipher.database.SQLiteQuery.<init>(SQLiteQuery.java:48)
        at net.sqlcipher.database.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:60)
        at net.sqlcipher.database.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:2016)
        at net.sqlcipher.database.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1902)
        at net.sqlcipher.database.SQLiteDatabase.keyDatabase(SQLiteDatabase.java:2669)
        at net.sqlcipher.database.SQLiteDatabase.openDatabaseInternal(SQLiteDatabase.java:2599)
        at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1247)
        at net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1322)
        at net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:166)
        at net.sqlcipher.database.SupportHelper.getWritableDatabase(SupportHelper.java:83)
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:706)
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:483)
        at ir.sharikapp.sharik.PartnersDao_Impl.idCounter(PartnersDao_Impl.java:224)
        at ir.sharikapp.sharik.SplashScreen.lambda$onCreate[=12=]$SplashScreen(SplashScreen.java:65)
        at ir.sharikapp.sharik.-$$Lambda$SplashScreen$OnSbGh2ZI8dgfCv5r4WYoYzo4YA.run(lambda)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6816)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1563)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1451)

有这方面资料的朋友,谢谢分享给我

我不确定你是否可以像这样使用成员变量,因为事实上,你不确定第一个变量是否在第二个变量之前被初始化。

尝试在 init 方法中初始化它们或像这样:

static String pass = null;
static byte[] passphrase = null;
static SupportFactory factory = null;

public static AppDatabase getAppDatabase(Context context) {
    if (pass == null) {
        pass = SplashScreen.sharedPreferences.getString("dbPass", null);
        passphrase = SQLiteDatabase.getBytes(pass.toCharArray());
        factory = new SupportFactory(passphrase);
    }    
    if (appDatabase == null) {
        appDatabase = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "dp_app")
            .addMigrations(MIGRATION_2_3)
            .allowMainThreadQueries()
            .openHelperFactory(factory)
            .build();
    }
    return appDatabase;
}

单独加密数据库的每条记录,您可以在加密数据库每条记录的函数中使用此程序包encrypt