Docs
The engine, in practice.
01
Overview
The QuorVault engine encrypts database columns with a hybrid scheme: X25519 + ML-KEM-768, combined through HKDF-SHA256, protect an AES-256 data key, which encrypts every value with AES-256-GCM, bound to its table, column and row.
It ships a command-line tool to migrate existing databases automatically, and a library for your application. The full guarantees are on the Security page.
02
Requirements
- Node.js 24.7+ (ships OpenSSL 3.5, ML-KEM and Argon2id)
- PostgreSQL (14+), MySQL (8.0+), MariaDB or SQLite
- A single-column primary key on each migrated table
03
Installation
npm install @quorvault/engine
npx quorvault --help04
The fast way
export DATABASE_URL="postgres://user:pass@host:5432/crm" # or mysql://… or sqlite:file.db
export QUORVAULT_PASSWORD="…" # otherwise prompted, hidden
# Is this machine, the database and the vault ready?
npx quorvault doctor
# Where is the personal data? (e-mails, phones, IBANs, cards, social security numbers…)
npx quorvault scan
# All-in-one for a table: vault, checks, rehearsal, encryption, search index, verification, report
npx quorvault protect --vault vault.json --table customers --columns email,phone --index email:lowerscan never prints the values it reads, only the kind of data it found. protect stops before writing if the rehearsal finds a problem, and resumes where it stopped when run again.
05
Step by step
# Secrets stay out of your shell history
export DATABASE_URL="postgres://user:pass@host:5432/crm"
export QUORVAULT_PASSWORD="…" # otherwise prompted, hidden
# 1. Create the vault (once per environment)
npx quorvault init --vault vault.json
# 2. Check the schema: column sizes, primary key, volume
npx quorvault plan --vault vault.json --table customers --columns email,phone
# 3. Dress rehearsal: reads and encrypts everything in memory, writes nothing
npx quorvault migrate --vault vault.json --table customers --columns email,phone --dry-run
# 4. Real migration (after a database backup)
npx quorvault migrate --vault vault.json --table customers --columns email,phone
# 5. Audit: every value must authenticate in its own row
npx quorvault verify --vault vault.json --table customers --columns email,phoneEach batch (500 rows by default) is locked, encrypted, verified and committed in one transaction. Re-running migrate resumes where it stopped. Columns that are too short or not text are flagged by plan; --alter-columns converts them to TEXT.
06
From a legacy cipher
export OLD_KEY="<64 hex characters>"
npx quorvault migrate … --from aes-256-cbc --legacy-key-env OLD_KEY
npx quorvault migrate … --from aes-256-gcm:hex --legacy-key-env OLD_KEYSupported formats: plaintext (default), aes-256-cbc, aes-256-gcm, with :hex when values are hex-encoded.
07
In your application
import { openVault } from "@quorvault/engine";
const qv = await openVault("vault.json", process.env.QUORVAULT_PASSWORD!);
// Write: token, plus a search fingerprint if the column is searchable
const { token, index } = qv.seal("alice@acme.io", { table: "customers", column: "email", row: 42 });
// Read
const email = qv.decrypt(row.email, { table: "customers", column: "email", row: row.id });
// Exact lookup on an encrypted column
db.query("SELECT * FROM customers WHERE email_bidx = $1", [qv.blindIndex(input, { table: "customers", column: "email" })]);
// During a rollout: accepts plaintext or ciphertext
const value = qv.decryptIfEncrypted(row.email, { table: "customers", column: "email", row: row.id });The vault password is provided at startup, from an environment variable or a secrets manager.
08
Searching an encrypted column
With --index email:lower, an email_bidx column receives a keyed fingerprint (HMAC-SHA256) of every value: WHERE email_bidx = ? finds the row, and the index is UNIQUE when the source column was. Without the vault the fingerprint reveals nothing; it only shows which rows share the same value. Modes: exact, lower (e-mails), digits (phones), compact (IBANs).
09
PHP, Python, Java…: the local service
export QUORVAULT_SERVE_TOKEN="$(openssl rand -base64 36)"
npx quorvault serve --vault vault.json # http://127.0.0.1:8750
curl -s http://127.0.0.1:8750/v1/encrypt \
-H "Authorization: Bearer $QUORVAULT_SERVE_TOKEN" -H "Content-Type: application/json" \
-d '{"table":"customers","column":"email","row":42,"value":"alice@acme.io"}'The service listens on the machine itself only, requires a token and sends no CORS headers. Endpoints: /v1/encrypt, /v1/decrypt, /v1/blind-index, with batching (items). Ready-made PHP and Python clients ship with the engine.
10
Key management
npx quorvault info --vault vault.json # vault details
npx quorvault passwd --vault vault.json # change the password (data untouched)
npx quorvault rotate-key --vault vault.json # new active data key
npx quorvault reencrypt --vault vault.json --table customers --columns email,phone
npx quorvault revert --vault vault.json --table customers --columns email,phoneBack up the vault and its password separately: without either, the data is permanently unreadable.
11
Token format
qv1.<base64url( keyId:uint32 ‖ nonce:12 bytes ‖ AES-256-GCM ciphertext ‖ tag:16 bytes )>
AAD = "quorvault/field/v1" ‖ vaultId ‖ keyId ‖ table ‖ column ‖ rowId (length-prefixed)ASCII text that fits in any TEXT column. A value copied elsewhere, or changed by a single byte, fails authentication.
12
Current limits
- No SQL Server, Oracle or MongoDB yet. MySQL: InnoDB tables only.
- Composite primary keys are not supported yet.
- On encrypted columns: exact lookups only (through the fingerprint), no sorting or prefix search.
