Working on a Kotlin Multiplatform project. Need to integrate SQLCipher, which we have done for the Android part, and now I am working on the iOS side.
I have built SQLCipher and integrated using c_interop.
SQLCipher documentation for Swift says this:
var rc: Int32
var db: OpaquePointer? = nil
var stmt: OpaquePointer? = nil
let password: String = "correct horse battery staple"
rc = sqlite3_open(":memory:", &db)
rc = sqlite3_key(db, password, Int32(password.utf8CString.count))
I was looking at this as it is easier to convert Swift to Kotlin, however since it is using the c_interop for KMM, I guess it uses the normal C code, which SQLCipher defines as this:
sqlite3 *db;
sqlite3_stmt *stmt;
bool sqlcipher_valid = NO;
if (sqlite3_open([databasePath UTF8String], &db) == SQLITE_OK) {
const char* key = [@"BIGSecret" UTF8String];
sqlite3_key(db, key, (int)strlen(key));
I have done this part in Kotlin like this:
var rc: Int
val db: CValuesRef<CPointerVar<cnames.structs.sqlite3>>? = null
val db2: CValuesRef<cnames.structs.sqlite3>? = null
if(sqlite3_open(databasePath, db) == SQLITE_OK) {
NSLog("Successfully opened connection to database")
}
rc = sqlite3_key(db2, null, 0)
However I keep getting crashes in XCode at sqlite3_open(databasePath, db)
It says Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
I'm assuming the issues are because of Pointers which I have no familiarity with really, and not sure how to convert to Kotlin.
From SQLCiphers example, they pass a string to sqlite3_key, however when I try pass a string it says Type mismatch: inferred type is String but CValuesRef<*>?
The first db I declare for sqlite3_open is a different type than the one for sqlite3_key, however their example shows them using the same db, so really not sure how exactly to proceed with this.
Full SQLCipher implementation is here
CodePudding user response:
I figured this out and hope this helps. First I had to do everything inside memscoped so we had access to alloc() methods.
Then I realised one issue was my database path was incorrect. Using XCode you can click on Window -> Devices & Simulators, click your app, then click the cog and press Download Container. Then inspect the container of your app to see where the database is. For me it was inside Application Support.
You can then check if your DB exists like so:
val fileManager = NSFileManager.defaultManager()
val doesDBExist = fileManager.fileExistsAtPath(databasePath)
Then define a pointer like this:
val db: CPointerVar<sqlite3> = allocPointerTo()
sqlite3_open actually creates a new db if it doesn't find one, so I used the v2 method to just check if it exists. Notice that we use .ptr to reference the pointer as it is passed in.
val openResult = sqlite3_open_v2(databasePath, db.ptr, SQLITE_OPEN_READWRITE, null)
Then if you want to check your key you can do this (Replace null with your key):
val keyResult = sqlite3_key(db.value, null, 0)
This should give you a result of 1, which is an error. As noted on the SQLCipher documentation, you need to perform a further step.
val result = sqlite3_exec(db.value, "SELECT count(*) FROM sqlite_master;", null, null, null)
This will try and perform a command on your DB, if your database is not encrypted, then should return fine using null. If you pass in a passphrase it should be able to query the DB.
We then need to close the db
sqlite3_close(db.value)
So the full method for me, when I wanted to check if my DB was encrypted or not, was like this:
override fun isDatabaseEncrypted(databasePath: String): Boolean {
memScoped {
val fileManager = NSFileManager.defaultManager()
val doesDBExist = fileManager.fileExistsAtPath(databasePath)
NSLog("Result is $doesDBExist")
val db: CPointerVar<sqlite3> = allocPointerTo()
val openResult = sqlite3_open_v2(databasePath, db.ptr, SQLITE_OPEN_READWRITE, null)
NSLog("openResult is $openResult")
val keyResult = sqlite3_key(db.value, null, 0)
NSLog("keyResult is $keyResult")
val result = sqlite3_exec(db.value, "SELECT count(*) FROM sqlite_master;", null, null, null)
sqlite3_close(db.value)
NSLog("Result is $result")
return result != SQLITE_OK
}
}
I know this is going into more detail than my original question, but hopefully it explains things a bit better for anybody who has similar questions to me.