0

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.

6
  • 1
    Looks like SplashScreen.sharedPreferences is null. Are you sure it has been instantiated at this stage? Commented May 2, 2022 at 8:35
  • Yes, I'm sure it's not null. Commented May 2, 2022 at 8:36
  • Please do not post screenshots of stacktraces, you can simply copy and paste the text content into your question and format it as code. Commented May 2, 2022 at 9:23
  • 1
    Get rid of the SharedPreferences. Do not store the passphrase in an unencrypted data store like SharedPreferences. Commented May 2, 2022 at 11:07
  • 1
    @KenWolf : I tested again and found that sharedPreferences is null.How can I fix this problem? Commented May 2, 2022 at 16:28

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

1 Comment

I think this is the best option to increase security because I also encrypted the SharedPreferences file to increase the security level of the software, which at least increased the API of the program, so there is no need to do this anymore.
1

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

7 Comments

I set the first version of the database to 2 and now I am writing the next version.
I'm talking about this static String pass = SplashScreen.sharedPreferences.getString("dbPass", null); static byte[] passphrase = SQLiteDatabase.getBytes(pass.toCharArray()); static SupportFactory factory = new SupportFactory(passphrase);
Yes, that's right. I took a test and found that it is null. How can I fix this problem?
I've edited my answer to give you an example
Your answer helped me. But I got an error again and changed the text of my questions completely. Please read again now and help me if you can. Thank you very much.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.