A small Go library for field-level encrypted persistence on top of SQLite.
Encrypted blobs cannot be queried. Extract the fields you need into normal SQLite columns; encrypt only the raw payload.
- AES-256-GCM encryption of arbitrary byte slices or JSON payloads
- OS keychain-backed master key storage (macOS Keychain, Windows Credential Manager, Linux Secret Service)
- Per-encryption random nonce — no nonce reuse
- Key versioning and rotation — old rows always decryptable
- Optional associated data (AAD) to bind ciphertext to a specific row context
- Schema helpers for the encrypted column pattern
- SQLCipher or full database file encryption
- Encrypted JSON querying or SQL rewriting
- ORM integration
- CGO dependency (uses
modernc.org/sqlite) - Cloud KMS
- Compression
go get github.com/berbyte/cryptlitestore, err := cryptlite.Open(cryptlite.Config{
DBPath: "app.db",
Keychain: cryptlite.KeychainConfig{
Service: "myapp",
Account: "default",
},
})
if err != nil {
return err
}
defer store.Close()
ctx := context.Background()
// Encrypt a JSON blob.
blob, err := store.EncryptJSON(ctx, rawJSON)
// Decrypt it later.
plaintext, err := store.DecryptJSON(ctx, *blob)
// Use AAD to bind ciphertext to a specific row.
blob, err = store.Encrypt(ctx, rawJSON, []byte("mytable/myjson/rowid-123"))
plaintext, err = store.Decrypt(ctx, *blob, []byte("mytable/myjson/rowid-123"))Extract queryable fields into normal columns. Encrypt the raw payload.
CREATE TABLE hook_invocations (
id TEXT PRIMARY KEY,
received_at INTEGER NOT NULL,
branch TEXT,
stage TEXT NOT NULL,
tool_name TEXT,
-- encrypted blob columns:
raw_input_json_ciphertext BLOB NOT NULL,
raw_input_json_nonce BLOB NOT NULL,
raw_input_json_key_version INTEGER NOT NULL
);
CREATE INDEX idx_hook_invocations_stage ON hook_invocations(stage, received_at);Insert flow:
- Receive raw JSON.
- Parse and extract fields needed for
WHERE/JOIN/ORDER BY. - Store extracted fields as normal columns.
store.Encrypt(ctx, rawJSON, aad)→ get*EncryptedBlob.- Store
Ciphertext,Nonce,KeyVersioncolumns.
The sqlite.EncryptedColumns helper generates the column definitions:
// Returns: "body_ciphertext BLOB NOT NULL, body_nonce BLOB NOT NULL, body_key_version INTEGER NOT NULL"
cols := sqlite.EncryptedColumns("body")On first Open, if no key exists in the keychain, the library generates a random 256-bit key and stores it. On subsequent opens it loads the existing key. Keys are versioned — v1, v2, etc.
// Rotate to a new key. Old rows remain decryptable.
newVersion, err := store.RotateKey(ctx)Pass a memory keyring for tests:
store, err := cryptlite.OpenWithKeyring(cfg, keyring.NewMemory())cryptlite protects encrypted blobs from offline inspection of the SQLite file. It does not protect against a compromised running process, malware on the machine, memory dumps, or a local user with live access. See docs/threat-model.md for details.
MIT