At-rest encryption
LiteORM opens an encrypted SQLite database through gosqlite’s transparent, page-level cipher: you pass a key when opening, and every page written to disk is ciphertext. Encryption is an open-time concern, orthogonal to how you use the data — once the database is open, the query builder, the orm, migrations, and search all work exactly as on an unencrypted database. For encrypting only specific columns, or for compression and multi-recipient access, see the comparison below.
Opening
Section titled “Opening”sqlite.OpenEncrypted(path, key) opens (or creates) an encrypted database with a 32-byte key, using the default Adiantum cipher. It takes the same variadic liteorm.Options as sqlite.Open, and the returned *liteorm.DB is used exactly like an unencrypted one:
import ( "liteorm.org/dialect/sqlite" "liteorm.org/orm")
db, err := sqlite.OpenEncrypted("app.db", key) // key is a 32-byte []byteif err != nil { return err}orm.AutoMigrate[Note](ctx, db)orm.NewRepo[Note](db).Create(ctx, &Note{Text: "…"})The on-disk file is ciphertext; reopening requires the same key, and a wrong key fails rather than returning garbage. For full control over the cipher, page size, pragmas, or pool sizing, use sqlite.OpenEncryptedConfig, which pairs a gosqlite.Config with crypto.Options from gosqlite.org/vfs/crypto:
import ( gosqlite "gosqlite.org" "gosqlite.org/vfs/crypto" "liteorm.org/dialect/sqlite")
db, err := sqlite.OpenEncryptedConfig( gosqlite.Config{Path: "app.db", Pragmas: gosqlite.RecommendedPragmas()}, crypto.Options{Key: key, Cipher: crypto.Adiantum},)The cipher is itself a VFS that the crypto package registers on open and tears down on Close, so cfg.VFS must be empty. The page-level format is stable, so a database encrypted by an earlier release opens with the same key.
Cipher choice
Section titled “Cipher choice”Two ciphers are available, both length-preserving so the on-disk file size matches the plaintext:
| Cipher | Key length | When |
|---|---|---|
crypto.Adiantum (default) |
32 bytes | The default. Wide-block, faster on CPUs without AES-NI, no AES side-channel surface. |
crypto.AESXTS |
64 bytes (two AES-256 keys) | Only when a compliance regime mandates AES. |
Prefer the Adiantum default. It is wide-block — a single flipped ciphertext bit garbles the whole page, so SQLite’s header parser fails fast on tampering rather than silently accepting a partially-corrupt page — and, being deterministic per page with no nonce, it leaks only whole-page equality to an offline attacker comparing two versions of a file, whereas AES-XTS leaks equality at 16-byte-block granularity. Pass crypto.AESXTS (with a 64-byte key) when AES is a hard requirement.
Key handling
Section titled “Key handling”The key is a raw secret, not a passphrase — source it from a key-management service or secret store, never a literal in source. To turn a passphrase into a correctly-sized key, derive one with crypto.DeriveKey(passphrase, salt, cipher) (Argon2id, sized to the cipher: 32 bytes for Adiantum, 64 for AES-XTS). Persist the salt alongside the file; it must be at least crypto.MinSaltLen (16 bytes) and unique per database.
key, err := crypto.DeriveKey(passphrase, salt, crypto.Adiantum) // 32-byte Adiantum keyLosing the key means losing the data — there is no recovery path. Rotating a key means re-encrypting: open the database with the old key and copy it into a fresh database opened with the new key.
Constraints
Section titled “Constraints”- Encryption needs an on-disk path;
:memory:is rejected, since there is nothing to encrypt at rest. - It is mutually exclusive with a custom VFS — the cipher is itself a VFS layer, so
cfg.VFSmust be empty. - It encrypts the whole database file, set at open time; there is no per-table encryption. For per-column encryption use a field codec.
- When reopening a database created with a non-default page size, set
crypto.Options.PageSizeto match itsPRAGMA page_size(it defaults to 4096); a mismatch fails to decrypt. - Confidentiality at rest only. The cipher has no MAC or integrity tag: a passive attacker with disk access recovers nothing without the key, but a write-capable attacker can flip ciphertext (SQLite usually sees this as corruption, but the cipher does not authenticate the data). Pair with disk-level integrity (LUKS dm-integrity, ZFS checksums) if active tampering is in scope.
Encryption vs vault vs field-codec encryption
Section titled “Encryption vs vault vs field-codec encryption”Three features encrypt SQLite data; they differ in what they encrypt and what else they bring. They compose freely.
| Scope | Bring | Reach for it when | |
|---|---|---|---|
sqlite.OpenEncrypted (this page) |
The whole database file, page-level | Single key, no format change, smallest dependency footprint | The whole database is sensitive and you want plain, transparent whole-file encryption. |
vault container |
The whole database file, in a container | Also compression, multi-recipient key wrapping, tamper-evidence | You additionally need to compress the file, share it across recipients, or want the container format. |
| Field codec encryption | One column | Plaintext to the app, ciphertext in that column; the rest of the row stays queryable | Only specific fields are sensitive and the rest should stay plaintext and indexable. |
They stack: a field-codec-encrypted column inside a database opened with OpenEncrypted is a perfectly ordinary setup. The vault container is a superset of OpenEncrypted’s encryption plus compression and access features — use OpenEncrypted when you want only whole-file encryption, and vault when you want the extras.
See also
Section titled “See also”examples/encryption— write encrypted, verify the on-disk bytes are ciphertext, reopen with the key, and watch the wrong key fail.- Which SQLite feature? — the decision front-door for the whole SQLite cluster.
- Compressed & encrypted databases — the vault container: whole-file encryption plus compression and multi-recipient access.
- Field codecs — encrypt a single column while the rest of the row stays plaintext.
- Backends reference — opening the SQLite backend and its options.