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.
Encrypt each record of your database separately , you can use this package encrypt in a function that encrypt each record of your databsae.