Using The SQLite Encryption Extension

The The SQLite Encryption Extension provides an easy way to create, read and write encrypted database files. It may be used with the SQLite Android bindings to add encrypted database capability to any application.

1. Building a SEE Enabled Version

Unless you are using a [./install.wiki#prebuilt | pre-built aar file] to use the SEE extension with the SQLite Android bindings you will need to build a custom version, either as a [./install.wiki#customaar | custom aar file] or by [./install.wiki#directint|directly integrating] the code with the application.

To do this, follow the instructions linked above. Except, before running the ndk-build command to build the native libraries:

  1. Replace the sqlite3.c and sqlite3.h files with the SEE enable versions (i.e. the concatenation of sqlite3.c and see.c - refer to the link above for details).
  2. Edit the Android.mk file so as to uncomment the second of the two lines reproduced below: # If using SEE, uncomment the following: # LOCAL_CFLAGS += -DSQLITE_HAS_CODEC

2. Application Code Notes

2.1. Opening an Encrypted Database

The best way to open an existing encrypted database, or to create a new one, is to specify an encryption key as part of an SQLite URI database identifier. For example, instead of "DatabaseName.db", one of: file:DatabaseName.db?key=secret file:DatabaseName.db?hexkey=0123ABCD

The first form above, specifying a text key, requires SQLite version 3.19.0.

Alternatively, after opening or creating an encrypted database, the application may immediately execute a PRAGMA to configure the encryption key. This must be done before any other database methods are called. For example: import org.sqlite.database.sqlite.SQLiteDatabase; ... SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase("my.db", null); db.execSQL("PRAGMA key = 'secretkey'");

Or, if you are using the SQLiteOpenHelper helper class, the PRAGMA must be the first thing executed within the onConfigure() callback. For example: import org.sqlite.database.sqlite.SQLiteDatabase; import org.sqlite.database.sqlite.SQLiteHelper; ... class MyHelper extends SQLiteOpenHelper { ... void onConfigure(SQLiteDatabase db){ db.execSQL("PRAGMA key = 'secretkey'"); } ... }

Note that using the PRAGMA to specify the encryption key as described above is incompatible with WAL mode. In Android, enabling WAL mode also enables connection pooling under the hood. This increase concurrency for multi-threaded applications, but also makes configuring the encryption key with SQLite directly using the PRAGMA unsafe (as Android may create and use new SQLite connections that have not been configured at any time).

Refer to the SEE documentation for further details regarding encryption keys.

2.2. Encrypting an Existing Database or Changing the Encryption Key

An unencrypted database may be encrypted, or the encryption key of an existing database changed using the "PRAGMA rekey" or "PRAGMA rehexkey" commands as described under "Using the "key" PRAGMA" in the SEE documentation.

If using WAL mode, this suffers from the same problem as "PRAGMA key" - after (re-)encrypting the database it only modifies the key used internally by one connection within the connection pool. Meaning that when Android attempts to use a different connection to access the database it throws a "file is encrypted or not a database" exception (SQLITE_NOTADB). Applications that need to modify the encryption key of a WAL mode database should therefore create a new SQLiteDatabase (or SQLiteOpenHelper) object to access the database, specifying the new key as part of the new URI identifier, immediately after running "PRAGMA rekey".

2.3. Other Differences From Non-SEE Builds

Aside from supporting encrypted databases, SEE-enabled builds behave differently in two more respects:

  1. In Android, if database corruption is encountered, or if an attempt is made to open a file that is not an SQLite database, the default behaviour is to delete the file and create an empty database file in its place. In a SEE-enabled build, the default behaviour is to throw an exception.

    The reason for this is that supplying an incorrect encryption key is indistinguishable from opening a file that is not a database file. And it seems too dangerous to simply delete the file in this case.

    The default behaviour can be overriden using the DatabaseErrorHandler interface.

  2. Earlier versions of this module disabled WAL mode connection pooling altogether for SEE-enabled builds. This changed here as part of the 3.19.0 development cycle.