SQLite.Encrypted for React Native

@sqlite-encrypted/react-native preserves the react-native-sqlite-storage API while packaging SQLite.Encrypted for Android and iOS.

Core rule: Activate SEE before calling openDatabase() with key.

Codec support: SQLite.Encrypted currently supports only aes256ofb. Pass a null codec to use aes256ofb; any codec supplied to key() or rekey() must be aes256ofb. Keyed opens use the same aes256ofb default.

Install

Configure the SQLite.Encrypted npm scope in the project's .npmrc:

@sqlite-encrypted:registry=https://packages.sourcegear.com/npm/

Then install the package:

npm install @sqlite-encrypted/react-native

React Native autolinking configures the native package. Install iOS pods after changing native dependencies.

npx pod-install

Do not also install react-native-sqlite-storage; both packages provide the same native module.

Promise API

import SQLite from '@sqlite-encrypted/react-native';

SQLite.enablePromise(true);

await SQLite.activateSEE(activationKey);

const database = await SQLite.openDatabase({
  name: 'application.db',
  location: 'default',
  key: databaseKey,
});

The keyed open routes through the native key implementation and rejects when that operation fails. Omit key for a plaintext database. Opening without key and immediately calling database.key() is the preferred alternative when the key is not available at open time.

Open with PRAGMA

Open without key, then execute the activation and key PRAGMAs before any schema query. Verify that the key PRAGMA returns one row containing ok.

import SQLite from '@sqlite-encrypted/react-native';

SQLite.enablePromise(true);

const database = await SQLite.openDatabase({
  name: 'legacy.db',
  location: 'default',
});

await database.executeSql(
  "PRAGMA activate_extensions = 'see-<activation-key>'"
);
const [keyResult] = await database.executeSql(
  "PRAGMA key = '<database-key>'"
);
const keyRow = keyResult.rows.length ? keyResult.rows.item(0) : null;
if (!keyRow || Object.values(keyRow)[0] !== 'ok') {
  throw new Error('SEE did not accept the database key');
}

On a 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 database.key() for new integrations.

Create, insert, and query

The ordinary react-native-sqlite-storage database API is unchanged.

await database.executeSql(
  'CREATE TABLE IF NOT EXISTS notes (' +
    'id INTEGER PRIMARY KEY, body TEXT NOT NULL)'
);

await database.executeSql(
  'INSERT INTO notes(body) VALUES (?)',
  ['hello']
);

const [result] = await database.executeSql(
  'SELECT id, body FROM notes ORDER BY id'
);

for (let index = 0; index < result.rows.length; index += 1) {
  console.log(result.rows.item(index));
}

Callback API

SQLite.enablePromise(false);

SQLite.activateSEE(
  activationKey,
  () => {
    SQLite.openDatabase(
      {
        name: 'application.db',
        location: 'default',
        key: databaseKey,
      },
      () => console.log('database opened'),
      (error) => console.error(error)
    );
  },
  (error) => console.error(error)
);

Text, binary, and null keys

With Promise mode enabled, the open database follows the configured Promise behavior.

await database.key(null, 'initial text key', null);

const binaryKey = Uint8Array.from([0x01, 0x00, 0x7f, 0xff]);
await database.rekey(null, binaryKey, null);

await database.rekey(null, 'replacement text key', null);
await database.rekey(null, null, null);

Strings use SEE text-key semantics. Uint8Array preserves exact bytes. Null removes encryption. Null database and codec values use the main database and the aes256ofb default.

In callback mode, append success and error callbacks.

database.rekey(
  null,
  'replacement key',
  null,
  () => console.log('rekey complete'),
  (error) => console.error(error)
);