5

我的应用程序目前正在使用房间数据库。我打算迁移以使用 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)手动删除表 n 迁移的命令,但它仍然无法正常工作。我的数据库创建代码如下。

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"
4

3 回答 3

2

sqlcipher_export您可以使用sqlcipher中的便捷方法加密未加密的数据库。因此,您不必使用fallbackToDestructiveMigration或花时间编写自定义迁移工具。

来自开发者的网站:

要使用 sqlcipher_export() 加密现有数据库,首先打开标准 SQLite 数据库,但不要提供密钥。接下来,附加一个新的加密数据库,然后在 SELECT 语句中调用 sqlcipher_export() 函数,传入要写入主数据库模式和数据的附加数据库的名称。

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

最后,安全地删除现有的明文数据库,然后像往常一样使用 sqlite3_key 或 PRAGMA 密钥打开新的加密数据库。

来源:https ://discuss.zetetic.net/t/how-to-encrypt-a-plaintext-sqlite-database-to-use-sqlcipher-and-avoid-file-is-encrypted-or-is-not-一个数据库错误/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 的此评论,其中解释了如何结合使用此getDatabaseState功能将数据从现有的纯文本数据库迁移到 sqlcipher 加密数据库。

于 2020-08-24T15:22:51.620 回答
1

这对我有用,但我觉得它不是最好的答案:

    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()

这是一个全新的数据库,需要全新填充数据。也许有一种方法可以在迁移中迁移它。

于 2020-07-27T23:34:44.643 回答
1

它可以固定为使用基于 SQLCipherUtils 数据库状态的 lib 中的 SQLCipherUtils.encrypt 方法,如下所述:

    @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

于 2020-10-16T05:26:53.813 回答