An SQLDelight driver that uses SQLite3MultipleCiphers for database encryption.
Define DatabasesDir
Common (available for all targets):
// Use system default location for the given platform
val databasesDir = DatabasesDir()
val databasesDir = DatabasesDir(null) // null
val databasesDir = DatabasesDir(" ") // blank
// Specify a path string
val databasesDir = DatabasesDir("/path/to/databases")
Android:
val databasesDir = context.databasesDir()
Jvm or Android:
val databasesDir = DatabasesDir(File("/path/to/databases"))
val databasesDir = DatabasesDir(Path.of("/path/to/databases"))
Define your SQLiteMCDriver.Factory
configuration in commonMain
NOTE: Realistically, your favorite singleton pattern or dependency injection should be utilized here.
// TLDR; 1 factory for each database file (will become evident later)
val factory = SQLiteMCDriver.Factory(dbName = "test.db", schema = TestDatabase.Schema) {
logger = { log -> println(log) }
// Will redact key/rekey values, disable for debugging or playing (default: true)
redactLogs = true
// SqlDelight AfterVersion migration hooks
afterVersions.add(AfterVersion(afterVersion = 2) { driver ->
// do something
})
afterVersion(of = 2) { driver ->
// do something
}
// Optional: Add PRAGMA statements to be executed
// upon each connection opening.
//
// See >> https://www.sqlite.org/pragma.html
pragmas {
// both ephemeral and filesystem connections
put("busy_timeout", 3_000.toString())
// ephemeral connections only
ephemeral.put("secure_delete", false.toString())
// filesystem connections only
filesystem.put("secure_delete", "fast")
}
// Can omit to simply go with the default DatabasesDir and
// EncryptionConfig (ChaCha20)
filesystem(databasesDir) {
encryption {
// e.g. coming from SQLCipher library
sqlCipher {
// v1()
// v2()
// v3()
v4()
// default()
}
}
}
}
// NOTE: Suspension function "create" alternative available
val driver1: SQLiteMcDriver = factory.createBlocking(Key.passphrase("password"))
driver1.close()
Easily spin up an ephemeral database for your configuration (no encryption)
// NOTE: Suspension function "create" alternative available
val inMemoryDriver = factory.createBlocking(opt = EphemeralOpt.IN_MEMORY)
val namedDriver = factory.createBlocking(opt = EphemeralOpt.NAMED)
val tempDriver = factory.createBlocking(opt = EphemeralOpt.TEMPORARY)
inMemoryDriver.close()
namedDriver.close()
tempDriver.close()
Easily change Key
s
// NOTE: Suspension function "create" alternative available
val driver2 = factory.createBlocking(key = Key.passphrase("password"), rekey = Key.passphrase("new password"))
driver2.close()
// Remove encryption entirely by passing an empty key (i.e. Key.passphrase(""))
val driver3 = factory.createBlocking(key = key.passphrase("new password"), rekey = Key.Empty)
driver3.close()
// Also supports use of RAW (already derived) keys and/or salt storage for SQLCipher & ChaCha20
val salt = getOrCreate16ByteSalt("user abc")
val derivedKey = derive32ByteKey(salt, "user secret password input")
val rawKey = Key.raw(key = derivedKey, salt = salt, fillKey = true)
val driver4 = factory.createBlocking(key = Key.Empty, rekey = rawKey)
driver4.close()
Easily migrate encryption configurations between software releases by defining a migrations block
val factory = SQLiteMCDriver.Factory(dbName = "test.db", schema = TestDatabase.Schema) {
logger = { log -> println(log) }
redactLogs = false
filesystem(databasesDir) {
// NOTE: Never modify migrations, just leave them
// for users who haven't opened your app in 5 years.
encryptionMigrations {
// Simply move your old encryption config up to a migration.
//
// If you _are_ migrating from SQLCipher library, note the
// version of SQLCipher used the first time your app was
// published with it. You will also need to define migrations
// all the way back for each possible version (v1, v2, v3),
// so that users who have not opened your app in a long time
// can migrate from those versions as well.
migrationFrom {
note = "Migration from SQLCipher library to sqlite-mc"
sqlCipher { v4() }
}
}
encryption {
sqlCipher { default() }
}
}
}
// Will try to open SQLCipher Default (legacy: 0).
//
// On failure, Will try to open using migrations in reverse order of
// what is expressed (i.e. SQLCipher-v4 (legacy: 4)).
//
// Once opened, will automatically migrate to SQLCipher Default (legacy: 0)
// using the same Key that it opened with.
val driverMigrate = factory.createBlocking(Key.passphrase("password"))
driverMigrate.close()
Share configurations between multiple factories
val customChaCha20 = EncryptionConfig.new(other = null) {
chaCha20 {
default {
// Define some non-default parameters if you wish
kdfIter(250_000)
}
}
// Other cipher choices
//
// ascon128 { default() }
//
// The following should not be utilized for new databases,
// but are there for migration purposes.
//
// rc4 { default() }
// wxAES128 { default() }
// wxAES256 { default() }
}
val migrationConfig = EncryptionMigrationConfig.new(other = null) {
migrationFrom {
note = "Migration from SQLCipher library to sqlite-mc"
sqlCipher { v4() }
}
migrationFrom {
note = "Migration from SQLCipher:default to ChaCha20"
sqlCipher { default() }
}
}
val sharedPragmas = PragmaConfig.new(other = null) {
put("busy_timeout", 5_000.toString())
}
val sharedFilesystem = FilesystemConfig.new(databasesDir) {
encryptionMigrations(migrationConfig)
encryption(customChaCha20)
}
val factory1 = SQLiteMCDriver.Factory("first.db", DatabaseFirst.Schema) {
pragmas(sharedPragmas)
filesystem(sharedFilesystem)
}
val factory2 = SQLiteMCDriver.Factory("second.db", DatabaseSecond.Schema) {
pragmas(sharedPragmas)
filesystem(sharedFilesystem)
}
NOTE: macOS
and Windows
binaries are code signed.
x86 | x86_64 | armv5 | armv6 | armv7 | arm64 | ppc64 | |
---|---|---|---|---|---|---|---|
Windows | ✔ | ✔ | |||||
macOS | ✔ | ✔ | |||||
Linux (libc) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
Linux (musl) | ✔ | ✔ | ✔ | ||||
FreeBSD |
Versioning follows the following pattern of SQLDelight
- SQLite3MultipleCiphers
- sqlite-mc sub version
a.b.c
- x.y.z
- s
s
is a single digit to allow for bug fixes and the likes
of 0
indicates a "first release" for that pairing of SQLDelight
and SQLite3MultipleCiphers
2.0.0
- 1.6.4
- 0
2.0.0
- 1.6.4
- 1
(an update with sqlite-mc
for 2.0.0-1.6.4
)2.0.1
- 1.6.4
- 0
(a minor version update with SQLDelight
)2.0.1
- 1.6.5
- 0
(a minor version update with SQLite3MultipleCiphers
)2.0.1
- 1.6.5
- 1
(an update with sqlite-mc
for 2.0.1-1.6.5
)SQLite3MultipleCiphers is compiled with the following flags
Jvm & Native:
SQLITE_HAVE_ISNAN=1
HAVE_USLEEP=1
SQLITE_ENABLE_COLUMN_METADATA=1
SQLITE_CORE=1
SQLITE_ENABLE_FTS3=1
SQLITE_ENABLE_FTS3_PARENTHESIS=1
SQLITE_ENABLE_FTS5=1
SQLITE_ENABLE_RTREE=1
SQLITE_ENABLE_STAT4=1
SQLITE_ENABLE_DBSTAT_VTAB=1
SQLITE_ENABLE_MATH_FUNCTIONS=1
SQLITE_DEFAULT_MEMSTATUS=0
SQLITE_DEFAULT_FILE_PERMISSIONS=0666
SQLITE_MAX_VARIABLE_NUMBER=250000
SQLITE_MAX_MMAP_SIZE=0
SQLITE_MAX_LENGTH=2147483647
SQLITE_MAX_COLUMN=32767
SQLITE_MAX_SQL_LENGTH=1073741824
SQLITE_MAX_FUNCTION_ARG=127
SQLITE_MAX_ATTACHED=125
SQLITE_MAX_PAGE_COUNT=4294967294
SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS
SQLITE_DQS=0
CODEC_TYPE=CODEC_TYPE_CHACHA20
SQLITE_ENABLE_EXTFUNC=1
SQLITE_ENABLE_REGEXP=1
SQLITE_TEMP_STORE=2
SQLITE_USE_URI=1
Jvm
SQLITE_THREADSAFE=1
Native:
SQLITE_THREADSAFE=2
SQLITE_OMIT_LOAD_EXTENSION
Darwin:
SQLITE_ENABLE_API_ARMOR
SQLITE_OMIT_AUTORESET
iOS, tvOS, watchOS:
SQLITE_ENABLE_LOCKING_STYLE=0
SQLDelight
gradle plugin and driver dependencies from your projectApply the sqlite-mc
gradle plugin.
plugins {
// Provides the SQLDelight gradle plugin automatically and applies it
id("io.toxicity.sqlite-mc") version("2.0.2-1.9.0-0")
}
// Will automatically:
// - Configure the latest SQLite dialect
// - Add the sqlite-mc driver dependency
// - Link native targets for provided SQLite3MultipleCiphers binaries
sqliteMC {
databases {
// Configure just like you would the SQLDelight plugin
}
}
dependencies {
// For android unit tests (NOT instrumented)
//
// This is simply the desktop binary resources needed for
// JDBC to operate locally on the machine.
testImplementation("io.toxicity.sqlite-mc:android-unit-test:2.0.2-1.9.0-0")
}