File is not a database (code 26)

Users say they get “file is not a database (code 26): , while compiling: SELECT COUN…”.
I can’t reproduce the error. I’ve read all the similar threads but haven’t found an answer. I hope you can help.

class DatabaseProvider @Inject constructor(
@param:Named("database_name") private val databaseName: String, //"cache.db"
private val context: Context,
private val sqlCipherKeyManager: SqlCipherKeyManager,
private val isCipherEnabled: Boolean
) {
@volatile
private var database: MyRoomDatabase? = null

@Synchronized
fun getDatabase(recreate: Boolean = false): MyRoomDatabase {
    if (recreate) {
        closeAndDelete()
    }
    return database ?: buildDatabase().also { database = it }
}

private fun closeAndDelete() {
    database?.close()
    deleteDatabaseFiles(context, databaseName)
    sqlCipherKeyManager.clearEncryptionKeys()
    database = null
}

private fun buildDatabase(): MyRoomDatabase {
    val builder = Room.databaseBuilder(context, MyRoomDatabase::class.java, databaseName)

    if (isCipherEnabled) {
        builder.openHelperFactory(sqlCipherKeyManager.getSupportFactory())
    }

    return builder
        .allowMainThreadQueries()
        .setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
        .addCallback(object : RoomDatabase.Callback() {
            override fun onOpen(db: SupportSQLiteDatabase) {
                super.onOpen(db)
                Log.d("DATABASE_LOG", "opened ${db.version}")
            }

            override fun onCreate(db: SupportSQLiteDatabase) {
                super.onCreate(db)
                Log.d("DATABASE_LOG", "created ${db.version}")
            }
        })
        .build()
}

private fun deleteDatabaseFiles(context: Context, databaseName: String) {
    val dbFile = context.getDatabasePath(databaseName)
    val filesToDelete = listOf(dbFile, File("${dbFile.absolutePath}-shm"), File("${dbFile.absolutePath}-wal"))
    filesToDelete.forEach { if (it.exists()) it.delete() }
}
}
class SqlCipherKeyManager @Inject constructor(
private val sharedPreferences: SharedPreferences,
private val errorReporter: ErrorReporter
) {
private val keyStore: KeyStore = KeyStore.getInstance(KEY_ANDROID_KEYSTORE).apply { load(null) }

init {
    System.loadLibrary("sqlcipher")
    initialize()
}

private fun initialize() {
    generateKeystoreKeyIfNeeded()
    if (!sharedPreferences.contains(KEY_ENCRYPTED)) {
        generateAndEncryptSqlCipherKey()
    }
}

private fun generateKeystoreKeyIfNeeded() {
    if (!keyStore.containsAlias(KEY_SQLCIPHER_KEYSTORE)) {
        val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_ANDROID_KEYSTORE)
        val keyGenSpec = KeyGenParameterSpec.Builder(
            KEY_SQLCIPHER_KEYSTORE,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .build()
        keyGenerator.init(keyGenSpec)
        keyGenerator.generateKey()
    }
}

private fun generateAndEncryptSqlCipherKey() {
    val secretKey = getSecretKey(KEY_SQLCIPHER_KEYSTORE)
    val cipher = getCipher()
    cipher.init(Cipher.ENCRYPT_MODE, secretKey)

    val sqlCipherKey = ByteArray(size = 32)
    SecureRandom().nextBytes(sqlCipherKey)

    val encryptedKey = cipher.doFinal(sqlCipherKey)
    val iv = cipher.iv

    sharedPreferences.edit {
        putString(KEY_ENCRYPTED, Base64.encodeToString(encryptedKey, Base64.NO_WRAP))
        putString(KEY_ENCRYPTION_IV, Base64.encodeToString(iv, Base64.NO_WRAP))
    }
    sqlCipherKey.fill(0)
}

private fun getDecryptedSqlCipherKey(keyAlias: String, key: String, iv: String): ByteArray {
    val encryptedKey = Base64.decode(key, Base64.NO_WRAP)
    val ivBytes = Base64.decode(iv, Base64.NO_WRAP)

    if (encryptedKey.isEmpty() || iv.isEmpty()) {
        throw CantOpenDatabaseException("Encryption key or IV is missing. Database cannot be opened.")
    }

    val secretKey = getSecretKey(keyAlias)
    val cipher = getCipher()
    cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(TAG_LENGTH, ivBytes))

    return cipher.doFinal(encryptedKey)
}

private fun getSecretKey(keyAlias: String): SecretKey =
    (keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry).secretKey

fun getSupportFactory(): SupportOpenHelperFactory {
    if (!keyStore.containsAlias(KEY_SQLCIPHER_KEYSTORE)) {
        initialize()
    }

    val encryptedKey = sharedPreferences.getString(KEY_ENCRYPTED, null).orEmpty()
    val iv = sharedPreferences.getString(KEY_ENCRYPTION_IV, null).orEmpty()
    val decryptedKey = getDecryptedSqlCipherKey(KEY_SQLCIPHER_KEYSTORE, encryptedKey, iv)
    return DisposableKeySupportFactory(decryptedKey)
}


fun clearEncryptionKeys() {
    try {
        sharedPreferences.edit {
            remove(KEY_ENCRYPTED)
            remove(KEY_ENCRYPTION_IV)
        }

        if (keyStore.containsAlias(KEY_SQLCIPHER_KEYSTORE)) {
            keyStore.deleteEntry(KEY_SQLCIPHER_KEYSTORE)
        }
    } catch (e: Exception) {
        errorReporter.reportError("$TAG clearEncryptionKeys", e)
    }
}

private fun getCipher() = Cipher.getInstance(CIPHER_TRANSFORMATION)

companion object {
    private const val KEY_ANDROID_KEYSTORE = "AndroidKeyStore"
    private const val KEY_SQLCIPHER_KEYSTORE = "sab_sqlcipher_keystore_key"
    private const val KEY_ENCRYPTED = "encrypted_key"
    private const val KEY_ENCRYPTION_IV = "encryption_iv"
    private const val CIPHER_TRANSFORMATION = "AES/GCM/NoPadding"
    private const val TAG_LENGTH = 128
}
}

When the application starts, a large number of API requests are made, the results of which are saved to the database.
And somewhere around this time, this error occurs.

I’m using net.zetetic:sqlcipher-android:4.13.0 and androidx.room:room-ktx:2.7.2 (not use implementation(group: ‘androidx.sqlite’, name: ‘sqlite’)

The invalid keys scenario seems unlikely to me, as keys are generated when they don’t exist, and this condition is met upon first login to the application. Keys are cleared only when a different user logs in. DatabaseProvider and SqlCipherKeyManager are created as singletons in the application.