Upgrading plain content provider data(backed by SQLite) to encrypted using SQLCipher -Android
Asked Answered
S

1

2

I am trying to encrypt data stored in my content provider with App upgrade. I followed below link for this,
How to implement SQLCipher when using SQLiteOpenHelper

While this works perfectly fine if a new installation is done, If I go for an upgrade, app crashes with below error

    net.sqlcipher.database.SQLiteException: file is encrypted or is not a database
at  android.app.ActivityThread.handleReceiver(ActivityThread.java:3009)
at  android.app.ActivityThread.access$1800(ActivityThread.java:177)
at  android.app.ActivityThread$H.handleMessage(ActivityThread.java:1526)
at  android.os.Handler.dispatchMessage(Handler.java:102)
at  android.os.Looper.loop(Looper.java:145)
at  android.app.ActivityThread.main(ActivityThread.java:5951)
at  java.lang.reflect.Method.invoke(Native Method)
at  java.lang.reflect.Method.invoke(Method.java:372)
at  com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1388)
at  com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)
    Caused by: net.sqlcipher.database.SQLiteException: file is encrypted or is not a database


I even tried to delete the existing tables and create a new table just to see if this solved the problem but no that was of no help either. Please suggest how to fix this, My Helper class is as below,

                public class MyDBHelper extends SQLiteOpenHelper {
                public MyDBHelper(Context context) {
                    super(context, DATABASE_NAME, null, DATABASE_VERSION);
                    SQLiteDatabase.loadLibs(context);
                }
                public static final String DATABASE_NAME = "mydb.db";

                private static final int DATABASE_VERSION = 2; // before attempting encryption it was 1

                @Override
                public void onCreate(SQLiteDatabase db) {
                    createTables(db);
                }

                @Override
                public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                    db.execSQL("DROP TABLE IF EXISTS " + "TEST_TABLE");
                    createTables(db);
                }

                private void createTables(SQLiteDatabase db){
                    db.execSQL(MyDBQueries.CREATE_TEST_TABLE);
                }
            }


My updated provider simply uses a key now to open the db as below,

                SQLiteDatabase db = databaseHelper.getWritableDatabase("encryptionKey");


As I mentioned, fresh installs works good, but upgrade crashes the app. Please suggest how to fix this.

Stallings answered 20/1, 2016 at 10:35 Comment(2)
Are you upgrading from a database that is already encrypted? Or are you upgrading from a database that is not yet encrypted? BTW, in the future, please post the entire stack trace.Emlin
Database is not encrypted initially, I want that to be encrypted with app upgrade.Stallings
E
6

Database is not encrypted initially, I want that to be encrypted with app upgrade

That is not going to happen automatically, and it cannot happen as part of SQLiteOpenHelper and its onUpgrade() path (as by the time onUpgrade() is called, SQLiteOpenHelper will have already attempted to open the unencrypted database, which will fail with the exception that you have shown).

You will need to separately encrypt that database. This code is what I have used:

public static void encrypt(Context ctxt, String dbName,
                             String passphrase) throws IOException {
    File originalFile=ctxt.getDatabasePath(dbName);

    if (originalFile.exists()) {
      File newFile=
          File.createTempFile("sqlcipherutils", "tmp",
                              ctxt.getCacheDir());
      SQLiteDatabase db=
          SQLiteDatabase.openDatabase(originalFile.getAbsolutePath(),
                                      "", null,
                                      SQLiteDatabase.OPEN_READWRITE);

      db.rawExecSQL(String.format("ATTACH DATABASE '%s' AS encrypted KEY '%s';",
                                  newFile.getAbsolutePath(), passphrase));
      db.rawExecSQL("SELECT sqlcipher_export('encrypted')");
      db.rawExecSQL("DETACH DATABASE encrypted;");

      int version=db.getVersion();

      db.close();

      db=
          SQLiteDatabase.openDatabase(newFile.getAbsolutePath(),
                                      passphrase, null,
                                      SQLiteDatabase.OPEN_READWRITE);
      db.setVersion(version);
      db.close();

      originalFile.delete();
      newFile.renameTo(originalFile);
    }
  }

After encrypt() returns, then you can go ahead and try to open the database.

Emlin answered 21/1, 2016 at 13:3 Comment(6)
Thank you for the valuable information. So, where do I call this utility? Further, how to ensure that it does not impact fresh installs of the app, because fresh install will not need this.Stallings
@Kaps: "So, where do I call this utility?" -- sometime after loadLibs() and sometime before getReadableDatabase() or getWriteableDatabase(). "Further, how to ensure that it does not impact fresh installs of the app, because fresh install will not need this" -- see if the database file already exists.Emlin
@CommonsWare, shouldn't you be creating the encrypted database in a different file and not renaming it to the original file? because in the subsequent runs, the originalFile.exists() will always be true. Instead isn't it better if the encrypted file is generated in another file instead?Gujarati
@AvinashR: This code presumes that you know that the database needs to be encrypted. The problem with changing to a different filename is that SQLiteOpenHelper wants to know the filename in the constructor, which makes it a bit complicated to create your SQLiteOpenHelper subclass. You are certainly welcome to have the encrypted file use a different name if you want; that is not how I approached this particular bit of sample code.Emlin
getting exception "net.sqlcipher.database.SQLiteException: error code 14: Could not open database". Please can someone help me! ThanksDysteleology
@sandeepmaaram: Ask a separate Stack Overflow question, where you provide a minimal reproducible example. This would include the complete Java stack trace along with your code that is generating the crash.Emlin

© 2022 - 2024 — McMap. All rights reserved.