SQLite.Encrypted for Android
SQLite.Encrypted for Android follows the SQLite Android API and packages the SEE native libraries for Android API 21 and newer.
Core rule: Call
SQLiteDatabase.activateSEE()once for the process before opening a database with an encryption key.
Codec support: SQLite.Encrypted currently supports only
aes256ofb. Omit the codec or passnullto useaes256ofb; any codec argument supplied tokey()orrekey()must beaes256ofb. Keyed opens use the sameaes256ofbdefault.
Install
The Maven coordinate is org.sqlite.encrypted:sqlite3. Add the SQLite.Encrypted repository and the dependency, replacing <version> with the release you use. Maven Central remains available for ordinary dependencies.
repositories {
google()
mavenCentral()
maven {
url = uri("https://packages.sourcegear.com/maven/releases")
}
}
dependencies {
implementation("org.sqlite.encrypted:sqlite3:<version>")
}
Open an encrypted database
The binding supports a keyed open, an explicit key() call, and the PRAGMA compatibility flow. Prefer the keyed open: it retains the key in the database configuration, applies it through the native key() implementation to every connection in the pool, and reports a failed key operation.
1. Open with a key
Load the JNI bridge, activate SEE, and pass the database key to the keyed openOrCreateDatabase() overload. The binding applies the key and validates the schema before returning each native connection.
import java.io.File;
import org.sqlite.database.sqlite.SQLiteDatabase;
System.loadLibrary("sqliteX");
SQLiteDatabase.activateSEE(activationKey);
File file = context.getDatabasePath("application.db");
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
file, null, databaseKey);
Pass a normal path to an unkeyed overload to open a plaintext database.
2. Open with API calls
Open the connection and call key() immediately, before reading the schema or running any other SQL. Reading the schema verifies that the supplied key can decrypt an existing database.
import java.io.File;
import org.sqlite.database.sqlite.SQLiteDatabase;
System.loadLibrary("sqliteX");
SQLiteDatabase.activateSEE(activationKey);
File file = context.getDatabasePath("application.db");
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(file, null);
try {
db.key(null, databaseKey, null);
db.rawQuery("SELECT count(*) FROM sqlite_schema", null).close();
} catch (RuntimeException error) {
db.close();
throw error;
}
This is most useful when the application directly owns a single connection. Use the keyed open overload when connection pooling or WAL may be enabled.
3. Open with PRAGMA
The connection-level PRAGMA flow is available for applications migrating from an existing integration. Run both statements before any schema access and verify that the key PRAGMA returns ok. Escape apostrophes in the database key before placing it in SQL text.
import java.io.File;
import android.database.Cursor;
import org.sqlite.database.sqlite.SQLiteDatabase;
import org.sqlite.database.sqlite.SQLiteException;
System.loadLibrary("sqliteX");
File file = context.getDatabasePath("application.db");
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(file, null);
try {
String escapedDatabaseKey = databaseKey.replace("'", "''");
db.execSQL(
"PRAGMA activate_extensions = 'see-" + activationKey + "'"
);
try (Cursor result = db.rawQuery(
"PRAGMA key = '" + escapedDatabaseKey + "'", null)) {
if (!result.moveToFirst() || !"ok".equals(result.getString(0))) {
throw new SQLiteException("SEE did not accept the database key");
}
}
db.rawQuery("SELECT count(*) FROM sqlite_schema", null).close();
} catch (RuntimeException error) {
db.close();
throw error;
}
On a build without encryption support, or when SEE cannot load the key, the key PRAGMA returns no row. If the result is ignored, the application can continue with a connection whose encryption key was never applied.
Query and update
The ordinary Android SQLite API is unchanged.
db.execSQL(
"CREATE TABLE IF NOT EXISTS notes "
+ "(id INTEGER PRIMARY KEY, body TEXT NOT NULL)"
);
db.execSQL("INSERT INTO notes(body) VALUES (?)", new Object[] {"hello"});
try (Cursor rows = db.rawQuery(
"SELECT id, body FROM notes ORDER BY id", null)) {
while (rows.moveToNext()) {
long id = rows.getLong(0);
String body = rows.getString(1);
}
}
Apply or rotate a key on an open connection
Text and binary overloads preserve SEE text-key and exact-byte semantics. Pass null for the database name and codec to use the main database and the aes256ofb default.
db.key(null, "initial text key", null);
db.rekey(null, "replacement text key", null);
byte[] binaryKey = new byte[] {0x01, 0x00, 0x7f, (byte) 0xff};
db.rekey(null, binaryKey, null);
A null key removes encryption. Cast a Java null literal because it is otherwise ambiguous between the String and byte[] overloads.
db.rekey(null, (String) null, null);
If the codec is supplied explicitly, the only supported value is aes256ofb.
db.rekey("main", "replacement key", "aes256ofb");
Use with Room
Room does not select this driver automatically. Supply a custom SupportSQLiteOpenHelper.Factory backed by this binding. Its SQLiteOpenHelper delegate receives the key so every pooled connection calls the native key() implementation.
SQLiteDatabase.activateSEE(activationKey);
AppDatabase database = Room.databaseBuilder(
context, AppDatabase.class, "application.db")
.openHelperFactory(new SeeOpenHelperFactory(databaseKey))
.setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
.build();
In this example, SeeOpenHelperFactory is the application's custom adapter. The key-applying portion passes the key to the binding's SQLiteOpenHelper constructor. The remaining adapter code forwards the Room lifecycle and database APIs to that delegate.
private SQLiteOpenHelper createDelegate(
SupportSQLiteOpenHelper.Configuration configuration,
String databaseKey) {
return new SQLiteOpenHelper(
configuration.context,
configuration.name,
null,
configuration.callback.version,
databaseKey,
null) {
// Forward onConfigure(), onCreate(), onUpgrade(), onDowngrade(),
// and onOpen() to configuration.callback.
};
}
The helper retains the key in the connection-pool configuration. Initial and later reader connections apply the key before any schema access. A failed key call or an unreadable schema aborts that connection's open.