I'm facing error when trying to migrate from android-database-sqlcipher to net.zetetic.database.sqlcipher

Hey there,

I’ve been using sqlcipher since very recently and the thing is, I’ve encountered the 16KB paging requirement and therefore I was lookin for fixes. And I came to find the new long term replacement.
Since im relatevely new to using this library, I wanted to try it out and decided to make some sample applications.
I tried to encrypt my sqlite database with a helper and my imports and including the library was done perfectly but, i still had one issue. In my previous code base i had something like this:

fun initializeDatabase(passphrase:String) { val db = getWritableDatabase(net.sqlcipher.database.SQLiteDatabase.getBytes(passphrase.toCharArray())) db.close() } fun createDatabaseIfNeeded(context: Context, dbName: String, passphrase: String) { try { val dbHelper = DatabaseHelper(context,dbName) dbHelper.initializeDatabase(passphrase) println("Database initialized and tables created successfully.") } catch (e: Exception) { println("Error creating database: ${e.message}") } }
And at time of reading or writing operations I found methods like

getWritableDatabase(key)

getReadableDatabase(key)

Now coming to the issue. I came up with this naive approach( I was experimenting out).

class MyDatabaseHelper(
    context: Context,
    private val databaseName: String,
    private val passphrase: String
) : SQLiteOpenHelper(context, databaseName, null, DATABASE_VERSION) 
{

    companion object {
        private const val DATABASE_VERSION = 1
        private const val TABLE_NAME = "my_data"
        private const val COLUMN_ID = "id"
        private const val COLUMN_TEXT_DATA = "text_data"
        private const val COLUMN_TIMESTAMP = "timestamp"
        private const val TAG = "MyDatabaseHelper"
    }

    override fun onCreate(db: SQLiteDatabase) {
        val CREATE_TABLE_QUERY = "CREATE TABLE $TABLE_NAME (" +
                "$COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT," +
                "$COLUMN_TEXT_DATA TEXT," +
                "$COLUMN_TIMESTAMP INTEGER DEFAULT 0)"
        db.execSQL(CREATE_TABLE_QUERY)
        Log.d(TAG, "Table '$TABLE_NAME' created.")
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        Log.d(TAG, "Upgrading database from version $oldVersion to $newVersion")
        when (oldVersion) {
            1 -> {
                val ADD_TIMESTAMP_COLUMN = "ALTER TABLE $TABLE_NAME ADD COLUMN $COLUMN_TIMESTAMP INTEGER DEFAULT 0"
                db.execSQL(ADD_TIMESTAMP_COLUMN)
                Log.d(TAG, "Migration: Added $COLUMN_TIMESTAMP to $TABLE_NAME")
            }
        }
    }

    fun insertData(data: String): Long {
        val db = this.writableDatabase//getWritableDatabase(passphrase)

        val contentValues = ContentValues().apply {
            put(COLUMN_TEXT_DATA, data)
            put(COLUMN_TIMESTAMP, System.currentTimeMillis())
        }
        val result = db.insert(TABLE_NAME, null, contentValues)
        db.close()
        Log.d(TAG, "Inserted data: '$data', Result: $result")
        return result
    }

    fun getAllData(): List<Pair<String, Long>> {
        val dataList = mutableListOf<Pair<String, Long>>()

        val db = readableDatabase//getReadableDatabase(passphrase)

        val cursor = db.rawQuery("SELECT $COLUMN_TEXT_DATA, $COLUMN_TIMESTAMP FROM $TABLE_NAME", null)

        cursor.use {
            if (it.moveToFirst()) {
                val textColumnIndex = it.getColumnIndex(COLUMN_TEXT_DATA)
                val timestampColumnIndex = it.getColumnIndex(COLUMN_TIMESTAMP)

                if (textColumnIndex != -1 && timestampColumnIndex != -1) {
                    do {
                        val textData = it.getString(textColumnIndex)
                        val timestamp = it.getLong(timestampColumnIndex)
                        dataList.add(Pair(textData, timestamp))
                    } while (it.moveToNext())
                }
            }
        }
        db.close()
        Log.d(TAG, "Retrieved data: $dataList")
        return dataList
    }

}

and turns out the helper was doing its thing but when I downloaded my db file I found out that it was never encrypted. Did i miss something? Please tell me if I need to change my approach to solve this.

Hi @HiesenBugHunter,

Unfortunately, there is not enough code to provide an answer about your integration. You may wish to review the SQLCipher for Android integration guidance here [1]. If that does not resolve your issue, you can prepare a full demo project uploaded to GitHub which showcases the problem you are having for further review.


  1. SQLCipher for Android - Full Database Encryption for SQLite on Android | Zetetic ↩︎

Hi @developernotes ,

I really appreciate your guidance on that the integration part. It really did help me. But I must say I needed a little refactor time to really dive in and understand all that documentation. It was a hard and confusing going through all that, especially since there were many tutorials that were using the old practices. And almost none with the new ones.
Thanks a lot :)).

Note for developers:
Btw I decided to share my fragments of the code here so that, it would be easier for someone like me walking this path.

It’s not much, just a simple crud functionality.

import android.content.ContentValues
import android.content.Context
import com.example.ciphertest.User
import net.zetetic.database.sqlcipher.SQLiteDatabase
import net.zetetic.database.sqlcipher.SQLiteDatabaseHook

class SqlCipherHelper(
    private val context: Context
)
{

    private val dbName = "secure.db"
    private val passphrase: ByteArray =
        "super_secure_password".toByteArray(Charsets.UTF_8)

    private var database: SQLiteDatabase? = null

    private fun dbPath(): String =
        context.getDatabasePath(dbName).absolutePath

    fun open() {
        if (database?.isOpen == true) return

        database = SQLiteDatabase.openOrCreateDatabase(
            dbPath(),
            passphrase,
            null,
            null,
            object : SQLiteDatabaseHook {
                override fun preKey(connection: net.zetetic.database.sqlcipher.SQLiteConnection) {}
                override fun postKey(connection: net.zetetic.database.sqlcipher.SQLiteConnection) {}
            }
        )

        createTables()
    }

    fun close() {
        database?.close()
        database = null
    }

    private fun createTables() {
        database?.execSQL(
            """
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                age INTEGER
            )
            """.trimIndent()
        )
    }






    // --------------------
    // CRUD OPERATIONS
    // --------------------

    fun insertUser(name: String, age: Int): Long {
        val values = ContentValues().apply {
            put("name", name)
            put("age", age)
        }
        return database!!.insert("users", null, values)
    }

    fun getAllUsers(): List<User> {
        val users = mutableListOf<User>()

        val cursor = database!!.rawQuery(
            "SELECT id, name, age FROM users",
            null
        )

        while (cursor.moveToNext()) {
            users.add(
                User(
                    id = cursor.getLong(0),
                    name = cursor.getString(1),
                    age = cursor.getInt(2)
                )
            )
        }

        cursor.close()
        return users
    }

    fun updateUser(id: Long, name: String, age: Int): Int {
        val values = ContentValues().apply {
            put("name", name)
            put("age", age)
        }

        return database!!.update(
            "users",
            values,
            "id = ?",
            arrayOf(id.toString())
        )
    }

    fun deleteUser(id: Long): Int {
        return database!!.delete(
            "users",
            "id = ?",
            arrayOf(id.toString())
        )
    }
}
data class User(
    val id: Long,
    val name: String,
    val age:Int
)

And this is how you can call it from your MainActivity:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        System.loadLibrary("sqlcipher")
        val helper = SqlCipherHelper(this)
        helper.open()
        helper.insertUser("Donald",69)
        helper.insertUser("Putin",67)
        val userList = helper.getAllUsers()
        for(user in userList){
            Log.i("test",user.name+" "+user.age)
        }
        helper.close()

    }

}

If you decide to eliminate these helper classes, you can directly proceed with an object that implements the SQLitedatabase. If anyone wants a snippet, I can share it aswell.
Thanks a lot SQLcipher Team:)

Cheers!