Encrypted SQLite With Ecto
TLDR⌗
Compiling SQLCipher:
./configure \
--enable-tempstore=yes \
--disable-tcl \
--enable-shared \
CFLAGS="-DSQLITE_HAS_CODEC -DSQLITE_THREADSAFE=1 -DSQLITE_USE_URI=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1 -DSQLITE_DQS=0 -DHAVE_USLEEP=1 -DALLOW_COVERING_INDEX_SCAN=1 -DENABLE_FTS3_PARENTHESIS=1 -DENABLE_LOAD_EXTENSION=1 -DENABLE_SOUNDEX=1 -DENABLE_STAT4=1 -DENABLE_UPDATE_DELETE_LIMIT=1 -DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_FTS4=1 -DSQLITE_ENABLE_FTS5=1 -DSQLITE_ENABLE_GEOPOLY=1 -DSQLITE_ENABLE_JSON1=1 -DSQLITE_ENABLE_MATH_FUNCTIONS=1 -DSQLITE_ENABLE_RBU=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_OMIT_DEPRECATED=1" \
LDFLAGS="-lcrypto"
Encrypting SQLite Databases⌗
SQLite is one of my favorite pieces of software in the world. It’s used in millions of projects and devices. One issue with the official public version of it is that there is no support for encryption natively. The primary decision for this seems to be for monetizing the work of the developers.
What this means is that to encrypt your SQLite database, you actually have to recompile the entire engine from source, with licensed files. You can read more about the official SQLite encryption on the official website
An alternative option to SQLite encryption that is free and open source is SQLCipher. It implements a similar API and to the offical SEE release, and also requires you to compile the engine from source.
Compiling SQLCipher⌗
This is the most time consuming part of the process. Luckily it only needs to be done once.
NOTE: this is specifically for your development environment. Compiling for production
*may* look the same to you, but it may not. This is specifically a problem for Nerves
which I will write a follow-up post about in the future.
The first step for compiling SQLCipher is getting the source. I chose to use the latest tagged release, a git clone will work the same.
wget https://github.com/sqlcipher/sqlcipher/archive/refs/tags/v4.5.0.tar.gz
tar xzf v4.5.0.tar.gz
cd sqlcipher-4.5.0
Next up, use autotools to configure the build. These are the settings I suggest starting with:
./configure \
--enable-tempstore=yes \
--disable-tcl \
--enable-shared \
CFLAGS="-DSQLITE_HAS_CODEC -DSQLITE_THREADSAFE=1 -DSQLITE_USE_URI=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1 -DSQLITE_DQS=0 -DHAVE_USLEEP=1 -DALLOW_COVERING_INDEX_SCAN=1 -DENABLE_FTS3_PARENTHESIS=1 -DENABLE_LOAD_EXTENSION=1 -DENABLE_SOUNDEX=1 -DENABLE_STAT4=1 -DENABLE_UPDATE_DELETE_LIMIT=1 -DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_FTS4=1 -DSQLITE_ENABLE_FTS5=1 -DSQLITE_ENABLE_GEOPOLY=1 -DSQLITE_ENABLE_JSON1=1 -DSQLITE_ENABLE_MATH_FUNCTIONS=1 -DSQLITE_ENABLE_RBU=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_OMIT_DEPRECATED=1" \
LDFLAGS="-lcrypto"
And finally, you just need to:
make
# May require root permissions
make install
Setup with Ecto⌗
Right before this post went live, I submitted two PRs to the Elixir SQLite driver:
The first allows for using externally compiled SQLite engine. This is required to use our previously compiled SQLCipher engine.
The second allows for setting the KEY
PRAGMA value. This is what SQLite SEE and SQLCipher
both use to decrypt data. It must be supplied before any of the SQLite database will be
accessable.
To have access to these new features, you will need to update to the latest version of exqlite
.
Add {:exqlite "~> 0.9"}
to your project, or simply update it with:
mix deps.update exqlite
To get exqlite
to use our SQLCipher installation, you need to export a handful of environment variables:
# tell exqlite that we wish to use some other sqlite installation. this will prevent sqlite3.c and friends from compiling
export EXQLITE_USE_SYSTEM=1
# Tell exqlite where to find the `sqlite3.h` file
export EXQLITE_SYSTEM_CFLAGS=-I/usr/local/include/sqlcipher
# tell exqlite which sqlite implementation to use
export EXQLITE_SYSTEM_LDFLAGS=-L/usr/local/lib -lsqlcipher
do a recompile with:
mix deps.compile exqlite --force
Almost done, the only other thing you have to do is supply a key
to the config. In config.exs
you can do something like:
config :my_app,
ecto_repos: [MyApp.Repo]
config :my_app, MyApp.Repo,
database: "path/to/my/database.db",
key: "test123" # add this line
And that’s it! Your data is now encrypted using the key provided.