SQLCipher + Room corruption issue

Hi, I’m having issues using SQLCipher on a newly created database w/ Room. I’m using a singleton to get the instance, and it works properly to write, delete, get data. But then, if I try to download the db from Android Device manager to open on DB Browser for SQLite for example, it asks for password but when I open it, it has no data at all, no tables at all and then it gets corrupted. When I try to open with Appspector, the database gets corrupted directly.

This is the code I’m using to retrieve the database, which occurs after the user types its password (which is on the server):

abstract class AppDatabase : RoomDatabase() {
    ...

    companion object : SingletonHolder<AppDatabase, Application>({ app ->

        Timber.v("Initializing DB")
        val passphrase: ByteArray = SQLiteDatabase.getBytes(app.tmpKey!!.toCharArray())
        val factory = SupportFactory(passphrase, null, false)

        val db =
            Room.databaseBuilder(
                app,
                AppDatabase::class.java,
                "example.db"
            )
            .openHelperFactory(factory)
        Timber.v("DB was successfully initialized")
        db.build()
    })
}

Opening w/ DB Browser causes:

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)

Opening w/ Appspector causes:

2020-04-09 16:56:49.269 29735-29845/xx E/SQLiteLog: (26) file is encrypted or is not a database
2020-04-09 16:56:49.278 29735-29845/xx E/DefaultDatabaseErrorHandler: Corruption reported by sqlite on database: /data/user/0/.../databases/test.db
2020-04-09 16:56:49.278 29735-29845/xx E/DefaultDatabaseErrorHandler: deleting the database file: /data/user/0/...databases/test.db

Hi @mendesdimas

What version of DB Browser are you using? The nighty builds support both SQLCipher 3 and 4 (the current major release of SQLCipher). Can you confirm what version of SQLCipher your application is using? This can be found by executing the following SQL command:

PRAGMA cipher_version;

@developernotes I could finally make it work on DB Browser (3.11.2) by using this pre-hook I found in the forums. But I don’t know exactly why as it identified the database as SQLCipher 4 beforehand.

val hook: SQLiteDatabaseHook = object : SQLiteDatabaseHook {
            override fun preKey(database: SQLiteDatabase) {
                database.rawExecSQL("PRAGMA kdf_iter = 5000")
            }

            override fun postKey(database: SQLiteDatabase) {
                //nothing
            }
        }

When I ran

PRAGMA cipher_version;

the output was

4.1.0 community

The only thing I’m trying to figure out right now is why the DefaultDatabaseErrorHandler is deleting the database when Appspector is accessing it for example. The same is happening on accessing it with Stheto. Is there a way to modify this error handler not to delete the database when the password is wrong, for example?

Hi @mendesdimas

database.rawExecSQL(“PRAGMA kdf_iter = 5000”)

That is a non-default KDF iteration length. Do you have a specific need for adjusting that? Please keep in mind that doing so may complicate your migration process during future major version releases of SQLCipher.

Currently the SupportHelper does not expose providing an alternative DatabaseErrorHandler as a constructor argument. This is a limitation of the Support API at the moment, as the SQLiteDatabase implementation allows for providing this at construction. We could certainly look to accommodate this in a future release.

1 Like

@developernotes thank you for that. I don’t have specific needs, the only proposal for that is for the team’s QA to verify whether the encryption work or not, and as he’s not a programmer we would need to use a software to read the exported database. About the default KDF iteration, should I use a specific one for sql cipher v4.1? That’s the first time I’m using it.

Also, do you have any idea on why DatabaseErrorHandler is being called upon accessing the database via these softwares? Stheto & Appspector.

Hi @mendesdimas

If you have no specific need to customize the behavior, I would suggest that you could remove the SQLiteDatabaseHook; it would be preferable to use the SQLCipher defaults.

I cannot speak to what Stheto and Appsepctor are doing, but you can see that onCorruption is called in places like this.

Thank you for that, I really appreaciate all the answers. And congratulations for the great library you allowed us developers to use.