SQLite.Encrypted for Apple Platforms

The SQLiteEncrypted Swift package re-exports SQLite.swift and links the SEE XCFramework for iOS, macOS, and Mac Catalyst.

Core rule: Call activateSEE(activationKey:) before constructing a Connection with key:.

Codec support: SQLite.Encrypted currently supports only aes256ofb. Omit the codec or pass nil to use aes256ofb; any codec argument supplied to a key or rekey call must be aes256ofb. Keyed initializers use the same aes256ofb default.

Install

In Xcode, choose File > Add Package Dependencies, enter the SQLiteEncrypted package URL, select the required release, and add the SQLiteEncrypted product to the application target.

A Package.swift dependency has the following form:

dependencies: [
    .package(
        url: "https://packages.sourcegear.com/swift/publisher/SQLiteEncrypted.git",
        from: "<version>"
    ),
]

Add the product to the target:

.target(
    name: "Application",
    dependencies: [
        .product(
            name: "SQLiteEncrypted",
            package: "SQLiteEncrypted"
        )
    ]
)

Open an encrypted database

Prefer the keyed initializer or open a connection and immediately call key(key:). Both routes use sqlite3_key_v3 and throw when the native key operation fails. Use PRAGMA primarily for compatibility with an existing integration.

Open with API calls

import SQLiteEncrypted

try activateSEE(activationKey: processActivationKey)

let database = try Connection(
    "/path/to/application.sqlite3",
    key: databaseKey
)

Activation is idempotent. The keyed initializer reports a failed native key operation as SQLiteEncryptedError.keyFailed. Omit key: for a plaintext database.

Open with PRAGMA

Open a connection without a key, then execute both PRAGMAs before reading the schema. Verify that the key PRAGMA returns ok. Escape apostrophes in the database key before placing it in SQL text.

import SQLiteEncrypted

enum DatabaseOpenError: Error {
    case keyNotAccepted
}

let database = try Connection("/path/to/application.sqlite3")
let escapedKey = databaseKey.replacingOccurrences(of: "'", with: "''")

try database.run(
    "PRAGMA activate_extensions = 'see-\(processActivationKey)'"
)
let keyResult = try database.scalar(
    "PRAGMA key = '\(escapedKey)'"
) as? String
guard keyResult == "ok" else {
    throw DatabaseOpenError.keyNotAccepted
}

On a build without encryption support, or when SEE cannot load the key, the key PRAGMA returns no value. Ignoring that result can leave the application using a connection whose encryption key was never applied.

Create, insert, and query

The ordinary SQLite.swift API is unchanged.

let notes = Table("notes")
let id = Expression<Int64>("id")
let body = Expression<String>("body")

try database.run(notes.create(ifNotExists: true) { table in
    table.column(id, primaryKey: .autoincrement)
    table.column(body)
})

try database.run(notes.insert(body <- "hello"))

for note in try database.prepare(notes.order(id)) {
    print("\(note[id]): \(note[body])")
}

Text, binary, and null keys

The overload that accepts String? uses SEE text-key semantics. The Data overload sends the exact bytes and byte count.

try database.key(key: "initial text key")

let binaryKey = Data([0x01, 0x00, 0x7f, 0xff])
try database.rekey(key: binaryKey)

try database.rekey(key: "replacement text key")

Passing nil removes encryption. Give nil a type so Swift selects the desired overload.

try database.rekey(key: nil as String?)

The database name and codec are optional. Supply the database name when working with an attached database. If the codec is supplied explicitly, the only supported value is aes256ofb.

try database.rekey(
    "main",
    key: "replacement key",
    codec: "aes256ofb"
)

Native C, Objective-C, and C++ callers

The XCFramework includes sqlite3.h and sqlite3_see.h. Include the SEE header for activation, key, and rekey declarations.

#include <sqlite3.h>
#include <sqlite3_see.h>

Do not define SQLITE_HAS_CODEC in the application build. The distributed header and native library expose the required declarations.