Search code examples
androiddatabaseencryptionpasswordsandroid-room

How can I encrypt the existing Room database with sqlcipher?


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.


Solution

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