
MVP JDBC wrapper around SQLCipher offering a JNI-backed custom JDBC driver, unified API, native bridge, build tooling, and CI-friendly encrypted DB samples.
Windows-first MVP for a Kotlin Multiplatform + JDBC wrapper around SQLCipher.
:kmp-api with unified common API and platform actuals:
net.zetetic:android-database-sqlcipher):jdbc-sqlcipher-jvm custom JDBC driver (jdbc:sqlcipher:)
META-INF/services/java.sql.Driver (no explicit Class.forName(...) needed):native-bridge JNI bridge + CMake build scriptssetString/setInt/setLong/setObject, execute/update/query):samples:kmp-basic-app basic KMP sample app using :kmp-api (encrypted DB + CRUD + wrong-key rejection checks):samples:kmp-sqldelight-app KMP + SQLDelight sample app (encrypted DB + CRUD + wrong-key rejection checks)kmp-api — common KMP API (expect/actual, JVM + Android actuals)jdbc-sqlcipher-jvm — JVM JDBC driver backed by JNInative-bridge — C JNI layer linked against SQLCipherthird_party/sqlcipher — SQLCipher amalgamation source locationsamples/kmp-basic-app — KMP sample through SqlCipherDatabaseFactory
samples/kmp-sqldelight-app — KMP SQLDelight sampleFor this MVP, native build expects SQLCipher amalgamation files:
third_party/sqlcipher/sqlite3.cthird_party/sqlcipher/sqlite3.hFull SQLCipher upstream source is kept as git submodule:
third_party/sqlcipher/upstreamInitialize/update it with:
git submodule update --init --recursive third_party/sqlcipher/upstreamRegenerate local amalgamation from submodule sources with:
./gradlew updateSqlcipherAmalgamation./gradlew updateSqlcipherAmalgamation -Psqlcipher.ref=v4.13.0
See third_party/sqlcipher/README.md.
SqlCipherDatabaseFactory is the unified KMP entrypoint exposed from commonMain.
initialize(platformContext)
open(...) (pass Android Context)open(path, key) and rekey(path, oldKey, newKey) are available with one stable API surface.Android usage sketch:
SqlCipherDatabaseFactory.initialize(applicationContext)
val db = SqlCipherDatabaseFactory.open("app.db", "secret")JVM usage sketch:
SqlCipherDatabaseFactory.initialize() // optional no-op
val db = SqlCipherDatabaseFactory.open("/abs/path/app.db", "secret")JVM call sites do not need manual driver bootstrap (Class.forName(...)); the SQLCipher JDBC driver is discovered through JDBC SPI.
-Dsqlcipher.native.path=<dir-or-file>-Dsqlcipher.native.lib.basename=<basename>sqlcipher.native.path), runtime now tries classpath-native resolution using:
META-INF/sqlcipher/native/<platform>/manifest.propertieswindows-x64: real native payload (JAR-embedded)linux-x64: real native payload (JAR-embedded)linux-arm64: real native payload (JAR-embedded)macos-x64: real native payload (JAR-embedded)macos-arm64: real native payload (JAR-embedded)Note on “releasing” native libs: JVM does not provide safe in-process unloading for loaded native libraries. The runtime does best-effort cleanup of extracted temp files at process shutdown.
./gradlew :sqlcipher-multiplatform-jdbc-core:compileKotlin :sqlcipher-multiplatform:compileKotlinJvm :samples:kmp-sqldelight-app:compileKotlinJvm./gradlew.bat :sqlcipher-multiplatform-jdbc-core:compileKotlin :sqlcipher-multiplatform:compileKotlinJvm :samples:kmp-sqldelight-app:compileKotlinJvm
Prerequisite:
python -m pip install conanconan profile detect --forceCore task:
./gradlew :native-bridge:printNativeConfig :native-bridge:buildNativeSupported Gradle properties:
-Pnative.target.os=windows|linux|macos-Pnative.target.arch=x64|arm64-Pnative.buildType=Release|RelWithDebInfo|Debug-Pnative.lib.basename=sqlcipher_jni (unified JNI output basename)-Pnative.conanExecutable=<path-or-command>-Pnative.conanProfile=<profile>-Pnative.conanBuildProfile=<profile>-Pnative.conanOutputDir=<path>Examples:
./gradlew.bat :native-bridge:buildNative -Pnative.target.os=windows -Pnative.target.arch=x64./gradlew :native-bridge:buildNative -Pnative.target.os=linux -Pnative.target.arch=x64./gradlew :native-bridge:buildNative -Pnative.target.os=macos -Pnative.target.arch=arm64./gradlew :jdbc-sqlcipher-jvm:nativeSmokeTestnativeSmokeTest automatically depends on :native-bridge:buildNative and wires:
sqlcipher.integration.enabled=truesqlcipher.native.path to the platform-appropriate native output foldersqlcipher.native.lib.basename
./gradlew :samples:kmp-basic-app:run
./gradlew :samples:kmp-sqldelight-app:run
All sample apps perform the same verification contract:
Each sample exposes verifySample task:
./gradlew :samples:kmp-basic-app:verifySample./gradlew :samples:kmp-sqldelight-app:verifySampleAggregate task from root project:
./gradlew verifySamplesConsole output format for all sample verifications is test-like and CI-friendly:
[TEST] <check name> when a check starts[PASS] <check name> when a check succeeds[FAIL] <check name> :: <reason> and process/task failure on any real check errorWrong-key verification is an expected negative test. During this check SQLCipher native layer may print ERROR CORE ... decrypt/HMAC messages; this is expected behavior for rejected keys and the check still prints [PASS] Wrong-key rejection when rejection is confirmed.
Note: SQLDelight sample sets scrubKeyMaterialAfterConnect=false in connection properties because SQLDelight's JDBC driver may reuse one Properties instance for future connections. Key bytes are manually zeroized in finally blocks.
Short Windows-first roadmap is available in:
Publishing/signing is wired through Gradle and GitHub Actions.
Required GitHub Secrets:
SIGNING_KEY (ASCII-armored private key)SIGNING_PASSWORDSIGNING_KEY_ID (recommended)MAVEN_CENTRAL_USERNAMEMAVEN_CENTRAL_PASSWORDOS-specific native build/publish strategy:
windows-x64, linux-x64, linux-arm64, macos-x64, macos-arm64.Published artifacts contract:
io.github.s0d3s.sqlcipher.multiplatform:jdbc-sqlcipher-jvm
io.github.s0d3s.sqlcipher.multiplatform:kmp-api
jdbc-sqlcipher-jvm (and therefore native artifacts).Release publishing endpoint strategy:
https://central.sonatype.com/repository/maven-snapshots/
https://central.sonatype.com/repository/maven-releases/
Start with Phase 1 (JDBC correctness) in this order:
PreparedStatement batch operationsautoCommit, commit, rollback)Windows-first MVP for a Kotlin Multiplatform + JDBC wrapper around SQLCipher.
:kmp-api with unified common API and platform actuals:
net.zetetic:android-database-sqlcipher):jdbc-sqlcipher-jvm custom JDBC driver (jdbc:sqlcipher:)
META-INF/services/java.sql.Driver (no explicit Class.forName(...) needed):native-bridge JNI bridge + CMake build scriptssetString/setInt/setLong/setObject, execute/update/query):samples:kmp-basic-app basic KMP sample app using :kmp-api (encrypted DB + CRUD + wrong-key rejection checks):samples:kmp-sqldelight-app KMP + SQLDelight sample app (encrypted DB + CRUD + wrong-key rejection checks)kmp-api — common KMP API (expect/actual, JVM + Android actuals)jdbc-sqlcipher-jvm — JVM JDBC driver backed by JNInative-bridge — C JNI layer linked against SQLCipherthird_party/sqlcipher — SQLCipher amalgamation source locationsamples/kmp-basic-app — KMP sample through SqlCipherDatabaseFactory
samples/kmp-sqldelight-app — KMP SQLDelight sampleFor this MVP, native build expects SQLCipher amalgamation files:
third_party/sqlcipher/sqlite3.cthird_party/sqlcipher/sqlite3.hFull SQLCipher upstream source is kept as git submodule:
third_party/sqlcipher/upstreamInitialize/update it with:
git submodule update --init --recursive third_party/sqlcipher/upstreamRegenerate local amalgamation from submodule sources with:
./gradlew updateSqlcipherAmalgamation./gradlew updateSqlcipherAmalgamation -Psqlcipher.ref=v4.13.0
See third_party/sqlcipher/README.md.
SqlCipherDatabaseFactory is the unified KMP entrypoint exposed from commonMain.
initialize(platformContext)
open(...) (pass Android Context)open(path, key) and rekey(path, oldKey, newKey) are available with one stable API surface.Android usage sketch:
SqlCipherDatabaseFactory.initialize(applicationContext)
val db = SqlCipherDatabaseFactory.open("app.db", "secret")JVM usage sketch:
SqlCipherDatabaseFactory.initialize() // optional no-op
val db = SqlCipherDatabaseFactory.open("/abs/path/app.db", "secret")JVM call sites do not need manual driver bootstrap (Class.forName(...)); the SQLCipher JDBC driver is discovered through JDBC SPI.
-Dsqlcipher.native.path=<dir-or-file>-Dsqlcipher.native.lib.basename=<basename>sqlcipher.native.path), runtime now tries classpath-native resolution using:
META-INF/sqlcipher/native/<platform>/manifest.propertieswindows-x64: real native payload (JAR-embedded)linux-x64: real native payload (JAR-embedded)linux-arm64: real native payload (JAR-embedded)macos-x64: real native payload (JAR-embedded)macos-arm64: real native payload (JAR-embedded)Note on “releasing” native libs: JVM does not provide safe in-process unloading for loaded native libraries. The runtime does best-effort cleanup of extracted temp files at process shutdown.
./gradlew :sqlcipher-multiplatform-jdbc-core:compileKotlin :sqlcipher-multiplatform:compileKotlinJvm :samples:kmp-sqldelight-app:compileKotlinJvm./gradlew.bat :sqlcipher-multiplatform-jdbc-core:compileKotlin :sqlcipher-multiplatform:compileKotlinJvm :samples:kmp-sqldelight-app:compileKotlinJvm
Prerequisite:
python -m pip install conanconan profile detect --forceCore task:
./gradlew :native-bridge:printNativeConfig :native-bridge:buildNativeSupported Gradle properties:
-Pnative.target.os=windows|linux|macos-Pnative.target.arch=x64|arm64-Pnative.buildType=Release|RelWithDebInfo|Debug-Pnative.lib.basename=sqlcipher_jni (unified JNI output basename)-Pnative.conanExecutable=<path-or-command>-Pnative.conanProfile=<profile>-Pnative.conanBuildProfile=<profile>-Pnative.conanOutputDir=<path>Examples:
./gradlew.bat :native-bridge:buildNative -Pnative.target.os=windows -Pnative.target.arch=x64./gradlew :native-bridge:buildNative -Pnative.target.os=linux -Pnative.target.arch=x64./gradlew :native-bridge:buildNative -Pnative.target.os=macos -Pnative.target.arch=arm64./gradlew :jdbc-sqlcipher-jvm:nativeSmokeTestnativeSmokeTest automatically depends on :native-bridge:buildNative and wires:
sqlcipher.integration.enabled=truesqlcipher.native.path to the platform-appropriate native output foldersqlcipher.native.lib.basename
./gradlew :samples:kmp-basic-app:run
./gradlew :samples:kmp-sqldelight-app:run
All sample apps perform the same verification contract:
Each sample exposes verifySample task:
./gradlew :samples:kmp-basic-app:verifySample./gradlew :samples:kmp-sqldelight-app:verifySampleAggregate task from root project:
./gradlew verifySamplesConsole output format for all sample verifications is test-like and CI-friendly:
[TEST] <check name> when a check starts[PASS] <check name> when a check succeeds[FAIL] <check name> :: <reason> and process/task failure on any real check errorWrong-key verification is an expected negative test. During this check SQLCipher native layer may print ERROR CORE ... decrypt/HMAC messages; this is expected behavior for rejected keys and the check still prints [PASS] Wrong-key rejection when rejection is confirmed.
Note: SQLDelight sample sets scrubKeyMaterialAfterConnect=false in connection properties because SQLDelight's JDBC driver may reuse one Properties instance for future connections. Key bytes are manually zeroized in finally blocks.
Short Windows-first roadmap is available in:
Publishing/signing is wired through Gradle and GitHub Actions.
Required GitHub Secrets:
SIGNING_KEY (ASCII-armored private key)SIGNING_PASSWORDSIGNING_KEY_ID (recommended)MAVEN_CENTRAL_USERNAMEMAVEN_CENTRAL_PASSWORDOS-specific native build/publish strategy:
windows-x64, linux-x64, linux-arm64, macos-x64, macos-arm64.Published artifacts contract:
io.github.s0d3s.sqlcipher.multiplatform:jdbc-sqlcipher-jvm
io.github.s0d3s.sqlcipher.multiplatform:kmp-api
jdbc-sqlcipher-jvm (and therefore native artifacts).Release publishing endpoint strategy:
https://central.sonatype.com/repository/maven-snapshots/
https://central.sonatype.com/repository/maven-releases/
Start with Phase 1 (JDBC correctness) in this order:
PreparedStatement batch operationsautoCommit, commit, rollback)