androidsqlcipher-android

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


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.


Solution

  • 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.