Переход с существующей базы данных комнаты на Sqlcipher

Мое приложение в настоящее время использует базу данных комнат. Я привязываюсь к переходу на использование базы данных Sqlcipher. У меня fallbackToDestructiveMigration() включен, но по-прежнему возникает следующая ошибка

java.lang.RuntimeException: Exception while computing database live data.
    at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:92)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
    at java.lang.Thread.run(Thread.java:764)
 Caused by: 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:91)
    at net.sqlcipher.database.SQLiteCompiledSql.<init>(SQLiteCompiledSql.java:64)
    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:2673)
    at net.sqlcipher.database.SQLiteDatabase.openDatabaseInternal(SQLiteDatabase.java:2603)
    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:476)
    at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
    at androidx.room.RoomDatabase.query(RoomDatabase.java:324)
    at androidx.room.util.DBUtil.query(DBUtil.java:83)
    at com.screenlocker.secure.room.MyDao_Impl$29.call(MyDao_Impl.java:1249)
    at com.screenlocker.secure.room.MyDao_Impl$29.call(MyDao_Impl.java:1246)
    at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
    at java.lang.Thread.run(Thread.java:764) 

Есть ли способ уничтожить всю мою базу данных и перейти на Sqlcipher? Я также пробовал команду database.delete("table_name",null,null) для ручного удаления таблиц при миграции, но она все еще не работает. Мой код для создания базы данных следующий.

DatabaseSecretProvider provider = new DatabaseSecretProvider(context);
        byte[] passphrase = provider.getOrCreateDatabaseSecret().asBytes();
        SupportFactory factory = new SupportFactory(passphrase);
        instance = Room.databaseBuilder(context, MyAppDatabase.class, AppConstants.DATABASE_NAME)
                .openHelperFactory(factory)
                .fallbackToDestructiveMigration()
                .build();

Я использую следующую версию Sqlcipher

implementation 'net.zetetic:android-database-sqlcipher:4.3.0'
    implementation "androidx.sqlite:sqlite:2.1.0"

person Muhammad Nadeem    schedule 13.02.2020    source источник


Ответы (3)


Вы можете зашифровать незашифрованную базу данных с помощью sqlcipher_export удобного метода из sqlcipher. Таким образом, вам не нужно использовать fallbackToDestructiveMigration или тратить время на написание собственного инструмента миграции.

С сайта разработчика:

Чтобы использовать sqlcipher_export () для шифрования существующей базы данных, сначала откройте стандартную базу данных SQLite, но не предоставляйте ключ. Затем ПРИСОЕДИНЯЙТЕСЬ к новой зашифрованной базе данных, а затем вызовите функцию sqlcipher_export () в операторе SELECT, передав имя присоединенной базы данных, в которую вы хотите записать основную схему базы данных и данные.

$ ./sqlcipher plaintext.db
sqlite> ATTACH DATABASE 'encrypted.db' AS encrypted KEY 'newkey';
sqlite> SELECT sqlcipher_export('encrypted');
sqlite> DETACH DATABASE encrypted;

Наконец, безопасно удалите существующую базу данных с открытым текстом, а затем откройте новую зашифрованную базу данных, как обычно, с помощью sqlite3_key или PRAGMA key.

Источник: https://discuss.zetetic.net/t/how-to-encrypt-a-plaintext-sqlite-database-to-use-sqlcipher-and-avoid-file-is-encrypted-or-is-not-a-database-errors/868

@commonsguy также содержит пример того, как это сделать в Android:

  /**
   * Replaces this database with a version encrypted with the supplied
   * passphrase, deleting the original. Do not call this while the database
   * is open, which includes during any Room migrations.
   *
   * @param ctxt a Context
   * @param originalFile a File pointing to the database
   * @param passphrase the passphrase from the user
   * @throws IOException
   */
  public static void encrypt(Context ctxt, File originalFile, byte[] passphrase)
    throws IOException {
    SQLiteDatabase.loadLibs(ctxt);

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

      db.close();

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

      final SQLiteStatement st=db.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''");

      st.bindString(1, originalFile.getAbsolutePath());
      st.execute();

      db.rawExecSQL("SELECT sqlcipher_export('main', 'plaintext')");
      db.rawExecSQL("DETACH DATABASE plaintext");
      db.setVersion(version);
      st.close();
      db.close();

      originalFile.delete();
      newFile.renameTo(originalFile);
    }
    else {
      throw new FileNotFoundException(originalFile.getAbsolutePath()+ " not found");
    }
  }

Источник: https://github.com/commonsguy/cwac-saferoom/blob/v1.2.1/saferoom/src/main/java/com/commonsware/cwac/saferoom/SQLCipherUtils.java#L175-L224

Вы можете передать context.getDatabasePath(DATABASE_NAME) как параметр originalFile.

Также см. это комментарий от commonsguy, в котором объясняется, как вы можете использовать это в сочетании с _ 7_ function для переноса данных из существующей базы данных с открытым текстом в базу данных, зашифрованную с помощью sqlcipher.

person Mad    schedule 24.08.2020

Это можно исправить, используя метод SQLCipherUtils.encrypt из библиотеки на основе состояния базы данных SQLCipherUtils, как указано ниже:

    @Synchronized
    fun getInstance(context: Context,key :String):  AppDB? {
       val  state=  SQLCipherUtils.getDatabaseState(context,
            DB_NAME)
       

        if (state == SQLCipherUtils.State.UNENCRYPTED) {
            
            SQLCipherUtils.encrypt(
                context,
                DB_NAME,
                Base64.decode(key, Base64.DEFAULT)
            )
        }

        if (INSTANCE == null) {
           
                val factory =
                    SafeHelperFactory(Base64.decode(key, Base64.DEFAULT))
                INSTANCE = Room.databaseBuilder(
                    context.applicationContext,
                    AppDB::class.java, DB_NAME
                )
                   
                    .openHelperFactory(factory)
                    .build()
                appContext = context.applicationContext
        
        }
        return INSTANCE
    }

обязательно вызовите SQLCipherUtils encrypt перед открытием БД

person mani    schedule 16.10.2020

Это сработало для меня, но я чувствую, что это не лучший ответ:

    val factory: SupportFactory = SupportFactory(masterKeyAlias.toByteArray())

    private fun buildDatabase(context: Context) =
        Room.databaseBuilder(
            context.applicationContext,
            AppDatabase::class.java,
            "MyDatabaseNew.db"  // <<--- change the name of this database file
        ).openHelperFactory(factory)
            .build()

Это совершенно новая база данных, и данные необходимо заполнять совершенно новыми. Возможно, есть способ перенести его в миграцию.

person Haider Malik    schedule 27.07.2020
comment
Да, я сделал то же самое, и все заработало. Это обходной путь. - person Muhammad Nadeem; 08.08.2020
comment
@MuhammadNadeem из того, что я читал в Интернете, похоже, что пароль неправильный, но я перепробовал все, что у меня был = / - person Haider Malik; 08.08.2020