SQLite.Encrypted for Expo
@sqlite-encrypted/expo preserves the Expo SQLite API and packages SEE for Android and iOS native builds. Web remains Expo's upstream web implementation and is not SEE-backed.
Core rule: Use a development or production build, activate SEE, and pass
keywhen opening the database. This package does not run in Expo Go.
Codec support: SQLite.Encrypted currently supports only
aes256ofb. Omit the codec or passnullto useaes256ofb; any codec supplied tokeySync/keyAsyncorrekeySync/rekeyAsyncmust beaes256ofb. Keyed opens use the sameaes256ofbdefault.
Install
Remove the official expo-sqlite package before installing this package. Both packages expose the native module named ExpoSQLite and cannot coexist.
Configure the SQLite.Encrypted npm scope in the project's .npmrc:
@sqlite-encrypted:registry=https://packages.sourcegear.com/npm/
Then install the package:
npm uninstall expo-sqlite
npx expo install @sqlite-encrypted/expo
npx expo prebuild
Existing source imports may remain unchanged by installing an npm alias.
{
"dependencies": {
"expo-sqlite": "npm:@sqlite-encrypted/expo@<version>"
}
}
Do not install both the alias and the scoped package.
Open an encrypted database
import * as SQLite from '@sqlite-encrypted/expo';
SQLite.activateSEE(processActivationKey);
const database = await SQLite.openDatabaseAsync('application.db', {
key: databaseKey,
});
The keyed open routes through the native key implementation and throws when that operation fails. Omit key for a plaintext database. String keys use SEE text-key semantics, matching keyAsync and rekeyAsync. Opening without key and immediately calling keyAsync is the preferred alternative when the key is not available at open time.
Open with PRAGMA
Native Android and iOS builds retain the connection-level PRAGMA sequence. Open without key, then execute both statements before schema access and verify that the key PRAGMA returns ok. Expo web does not use SEE.
import * as SQLite from '@sqlite-encrypted/expo';
const database = await SQLite.openDatabaseAsync('legacy.db');
await database.execAsync(
"PRAGMA activate_extensions = 'see-<activation-key>'"
);
const keyRow = await database.getFirstAsync<Record<string, string>>(
"PRAGMA key = '<database-key>'"
);
if (!keyRow || Object.values(keyRow)[0] !== 'ok') {
throw new Error('SEE did not accept the database key');
}
On a native build without encryption support, or when SEE cannot load the key, the key PRAGMA returns no row. Ignoring that result can leave the application using a connection whose encryption key was never applied. Prefer keyed open or keyAsync/keySync for new integrations.
Create, insert, and query
The ordinary Expo SQLite database API is unchanged.
await database.execAsync(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY,
body TEXT NOT NULL
)
`);
await database.runAsync(
'INSERT INTO notes(body) VALUES (?)',
'hello'
);
const notes = await database.getAllAsync<{ id: number; body: string }>(
'SELECT id, body FROM notes ORDER BY id'
);
Async key and rekey
await database.keyAsync(null, 'initial text key');
const binaryKey = Uint8Array.from([0x01, 0x00, 0x7f, 0xff]);
await database.rekeyAsync(null, binaryKey);
await database.rekeyAsync(null, 'replacement text key');
await database.rekeyAsync(null, null);
Sync key and rekey
const syncDatabase = SQLite.openDatabaseSync('sync.db');
syncDatabase.keySync(null, 'initial text key');
syncDatabase.rekeySync(null, binaryKey);
syncDatabase.rekeySync(null, 'replacement text key');
syncDatabase.rekeySync(null, null);
Strings use SEE text-key semantics. Uint8Array preserves exact bytes. Null removes encryption. If the third argument is supplied, it must be aes256ofb.
await database.rekeyAsync('main', 'replacement key', 'aes256ofb');
Encrypted key-value storage
SEEStorage is compatible with Expo's asynchronous and synchronous SQLite storage methods. It requires a non-empty key and prior SEE activation.
import SEEStorage from '@sqlite-encrypted/expo/see-kv-store';
const storage = new SEEStorage('application-storage.db', {
key: databaseKey,
});
await storage.setItemAsync('session', 'encrypted value');
const session = await storage.getItemAsync('session');
The ordinary @sqlite-encrypted/expo/kv-store entry defaults to plaintext unless open options are supplied.