zeekayandHanzo Dev 8a45f8af04 Convert a database without the C library
The copy was SQLCipher's sqlcipher_export, a SQL function only the C libsqlcipher
defines. So a build without that library could not convert a plaintext database at
all: the store it was pointed at stayed unopenable, and every test covering the
conversion skipped rather than ran.

Nothing else here needed the C engine. github.com/hanzoai/sqlite encrypts on every
build — the pure-Go SQLCipher codec envelope, byte-compatible with the C one — so
the conversion was the last place the dependency survived, and it did not have to.

It is now a logical copy in Go over two ordinary handles: the plaintext source
opened with no key, a fresh encrypted target opened with one. Tables, then rows,
then indexes and triggers and views, so an index is not rebuilt per insert and a
trigger cannot fire on rows the source never had. Columns are named from
table_xinfo rather than SELECT * — a generated column cannot be inserted into, and
naming them also stops a normalised column order from writing values into the
wrong column. The AUTOINCREMENT counters come across from sqlite_sequence, which
takes a delete and an insert because that table carries no unique index for an
ON CONFLICT target to name.

A byte-level encrypt is not available for this: SQLCipher reserves 80 bytes per
page for its IV and tag, a plaintext database reserves none, and no pragma
retrofits that into an existing file.

A virtual table is REFUSED, naming itself. Its shadow tables hold an index layout
rather than rows, so recreating it regenerates bytes no logical copy can promise
and verify would report a difference the data does not explain. The C function did
that from inside the engine.

The suite now runs on the pure-Go build with nothing skipped — 30 tests, including
fidelity over AUTOINCREMENT, WITHOUT ROWID, a GENERATED column and foreign keys —
and passes identically against a real libsqlcipher. A store converted by either
engine reads back under the other, rows, header fields and counters intact.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-12 20:08:39 -07:00

cek

Opens a namespace's SQLite database encrypted at rest.

cek.SetMaster(k)                            // once, at boot, from KMS
db, err := cek.Open(ns, "treasury", dataDir) // everywhere else

The key is derived from the master and the namespace. It is not generated, not wrapped, not stored, and not rotated in place — so there is no unwrap step, no rewrap step, no per-file key material to lose, and no migration path to maintain. A database is born encrypted or it does not exist.

Losing the master loses the data. That is the property you want from encryption at rest, and the reason the master lives in KMS.

Where it sits

hanzoai/namespace names the entity, and where its file lives
hanzoai/cek turns the master + that name into the file's key, and opens it
hanzoai/sqlite opens a file under a raw key; knows nothing about who owns it
hanzoai/kms holds the master

Nothing here knows about orgs, users, billing or plugins. It knows a namespace, a subsystem, and a master key.

S
Description
No description provided
Readme MIT
128 KiB
Languages
Go 100%