Home > Software design >  How can I encrypt the existing Room database with sqlcipher?
How can I encrypt the existing Room database with sqlcipher?

Time:05-05

I have been looking for a solution for Room database security for a while and got acquainted with sqlcipher but could not find a suitable solution to use it.

Until finally to the address Link I arrived and with the help of the training on this site, I was able to do things.

I wrote the code like this:

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

This line of code captures the password that is stored in sharedPreferences the first time the software is run.


Inside the migration and in the queue

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

and it might be a problem.


In addition, if you see the example mentioned in the Link above, the migration method is written as follows

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

But I could not use database.rawExecSQL because when I was writing this code, android studio underlined KEY as an error; That's why I used database.query. Could this be my fault?


When I run the program, I get an error that I put under its 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$0$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)

Friends who have information about this, thank you for sharing it with me.

CodePudding user response:

I'm not sure you can use members' variables like this because, in fact, you are not sure the 1st var is initialized before the 2nd one.

Try to init these in an init method or like this:

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;
}

CodePudding user response:

Encrypt each record of your database separately , you can use this package encrypt in a function that encrypt each record of your databsae.

  • Related