SQLite.Encrypted for .NET and NuGet

The SQLite.Encrypted NuGet package supplies the official SQLite Encryption Extension native library as e_sqlite3. Use it with a managed SQLite provider that can load the SQLitePCLRaw e_sqlite3 provider, such as Microsoft.Data.Sqlite.Core.

The package does not replace your managed database API. Connections, commands, parameters, transactions, and data readers continue to use that API normally.

Core rule: Select the e_sqlite3 provider before opening a connection, activate SEE before loading a database key, and verify that PRAGMA key returns ok before accessing the schema.

Codec support: The SQLite.Encrypted native library currently supports only aes256ofb. (In the past, SEE builds from SourceGear defaulted to aes128ofb.) If a managed provider exposes a codec option, omit it to use aes256ofb or specify only aes256ofb.

Install

Register the SQLite.Encrypted NuGet feed once for the solution or development environment:

dotnet nuget add source `
  https://packages.sourcegear.com/nuget/index.json `
  --name SQLiteEncrypted

Add the native package, a managed provider, and the SQLitePCLRaw provider that loads e_sqlite3:

dotnet add package SQLite.Encrypted `
  --version <version> `
  --source https://packages.sourcegear.com/nuget/index.json
dotnet add package Microsoft.Data.Sqlite.Core
dotnet add package SQLitePCLRaw.provider.e_sqlite3

Use the same SQLite.Encrypted version across every project that contributes native SQLite assets to the application. Do not add a SQLitePCLRaw bundle or another package that supplies a competing e_sqlite3 library.

Select the native provider

Configure SQLitePCLRaw once during process startup, before constructing any SQLite connection:

using SQLitePCL;

raw.SetProvider(new SQLite3Provider_e_sqlite3());
raw.FreezeProvider();

FreezeProvider() prevents later code from silently replacing the provider. Call this setup from one application-owned startup path rather than from every database service.

Open an encrypted database

The managed providers do not add a SQLite.Encrypted keyed-open overload. Open the connection, activate SEE, apply the key, and check the key result before running migrations or any other schema operation.

using Microsoft.Data.Sqlite;
using SQLitePCL;

raw.SetProvider(new SQLite3Provider_e_sqlite3());
raw.FreezeProvider();

using var database = new SqliteConnection("Data Source=application.db");
database.Open();

using (var activate = database.CreateCommand())
{
    activate.CommandText =
        $"PRAGMA activate_extensions = 'see-{activationKey}'";
    activate.ExecuteNonQuery();
}

var escapedDatabaseKey = databaseKey.Replace("'", "''");
using (var key = database.CreateCommand())
{
    key.CommandText = $"PRAGMA key = '{escapedDatabaseKey}'";
    if (!string.Equals(
            key.ExecuteScalar()?.ToString(),
            "ok",
            StringComparison.Ordinal))
    {
        throw new InvalidOperationException(
            "SEE did not accept the database key");
    }
}

// It is now safe to run ordinary schema and data commands.
using var create = database.CreateCommand();
create.CommandText = """
    CREATE TABLE IF NOT EXISTS notes(
        id INTEGER PRIMARY KEY,
        body TEXT NOT NULL
    )
    """;
create.ExecuteNonQuery();

The activation PRAGMA requires the see- prefix. The raw activation key stored in configuration should not include that prefix.

If SEE is unavailable or cannot load the database key, PRAGMA key returns no row. Ignoring the result can allow application code to continue on a connection whose encryption key was never applied and may result in a plaintext database.

Query and update

After the key is accepted, the ordinary managed SQLite API is unchanged.

using var insert = database.CreateCommand();
insert.CommandText = "INSERT INTO notes(body) VALUES ($body)";
insert.Parameters.AddWithValue("$body", "hello");
insert.ExecuteNonQuery();

using var query = database.CreateCommand();
query.CommandText = "SELECT id, body FROM notes ORDER BY id";
using var rows = query.ExecuteReader();
while (rows.Read())
{
    long id = rows.GetInt64(0);
    string body = rows.GetString(1);
}

Rotate or remove the database key

Check PRAGMA rekey in the same way as PRAGMA key. An empty rekey value removes database encryption.

static void ExpectOk(SqliteCommand command)
{
    if (!string.Equals(
            command.ExecuteScalar()?.ToString(),
            "ok",
            StringComparison.Ordinal))
    {
        throw new InvalidOperationException(
            "SEE did not accept the key operation");
    }
}

var escapedReplacementKey = replacementKey.Replace("'", "''");
using (var rekey = database.CreateCommand())
{
    rekey.CommandText = $"PRAGMA rekey = '{escapedReplacementKey}'";
    ExpectOk(rekey);
}

using (var decrypt = database.CreateCommand())
{
    decrypt.CommandText = "PRAGMA rekey = ''";
    decrypt.ExecuteNonQuery();
}

Close every other connection to the database before rotating its key. Reopen the database with the replacement key as part of the application's release verification. An empty PRAGMA rekey removes encryption and does not return ok; verify it by closing the connection and reopening the database without a key.

Use another managed provider

The same native package can be used by other .NET SQLite libraries when they can be configured to load e_sqlite3. Keep these responsibilities separate:

  1. SQLite.Encrypted supplies the native SEE-enabled library.
  2. The provider selects and calls that native library.
  3. The managed SQLite library supplies its normal connection and command API.

Do not initialize a provider that loads the operating system SQLite library or a different bundled SQLite binary. Provider-specific connection handling remains documented by that provider's project.

Deployment

Publish for a concrete runtime identifier so .NET selects the matching native asset:

dotnet publish -c Release -r win-x64 --self-contained false
dotnet publish -c Release -r linux-x64 --self-contained false
dotnet publish -c Release -r osx-arm64 --self-contained false

Verify the publish directory contains the expected e_sqlite3 native library and that no second package has overwritten it. The supported runtime assets are listed in the SQLite.Encrypted package for each release.

Keep keys out of source control

Load the SEE activation key and database key from secure configuration or a platform credential store. Do not put either key in a connection string, log message, checked-in settings file, or command-line argument.