CBF is how structured fields leave RAM
Scheduled9 min read By NT²
A credential is a map of fields while you edit it. The moment those fields must survive a lock, a restart, or a replica, they need a portable ciphertext shape—not a JSON dump waiting for the next reader.
CBF is how structured fields leave RAM
Claim: structured fields need a portable ciphertext shape
While a vault is unlocked, an item is useful as structure. A credential has a username, a password, a URL, maybe a one-time code. A note has typed text. A bank or document template may carry dozens of named placements. The UI edits a field map. Domain logic validates that map. Memory is allowed to hold plaintext because the user asked for it.
At rest, that map must stop being a map.
NT² Vault does not persist item payloads as readable JSON beside titles, and it does not invent a new serialization for every channel that might carry an item—SQLite today, a backup file tomorrow, a replica batch later. Sensitive fields leave RAM as CBF: a compact, portable ciphertext container. Inside the cleartext layer sits a payload blob—structured fields encoded against a stable field registry. Around that blob sits authenticated encryption with AES-GCM, and optionally compression with zstd before the seal.
The product claim is narrow and deliberate: CBF is how structured fields leave RAM. Envelope encryption decides which key seals an object. CBF decides what bytes that key is sealing, and how those bytes stay portable across storage and transport without becoming someone else's plaintext schema.
The constraint: JSON at rest is not a privacy boundary
A structured vault tempts a shortcut. Encode the field map as JSON. Store the
string in a column. Call the column payload. Encrypt “later,” or encrypt the
whole row with a vault-wide key and treat the intermediate format as an
implementation detail.
That shortcut fails in several ways at once.
Readable intermediates invite accidental disclosure. If the durable representation is still a field map—only base64-wrapped, only gzipped, only “inside an encrypted database file”—every backup tool, sync debugger, and support dump becomes a potential plaintext leak. Zero-knowledge is not a marketing adjective for “we encrypt the database file.” It is a rule about what exists on disk when the vault is locked.
Ad-hoc encryption of JSON is brittle. Stringify, AES-GCM, store ciphertext and IV. That works for one app version. The next version adds a field, renames a key, nests a composite value, or needs to share a subset of fields. Without a versioned layout and a field registry, every consumer reinvents parsing. Without authenticated framing, “ciphertext” can become an opaque blob that still depends on undocumented conventions about character encoding, key order, and which keys are secret.
Channels multiply serializers. Local SQLite wants one shape. Backup wants another. Deep links and share packages want a third. If each path invents its own JSON dialect, integrity checks diverge, migrations fork, and a bug in one channel can leave another channel holding a different idea of “the same item.”
Size and CPU matter without becoming the story. Structured payloads are often repetitive text. Naïve JSON grows quickly. Compressing after encryption does nothing useful. Compressing before encryption can help, but only if the format marks whether compression was applied and fails closed when it was not.
NT² Vault needed one answer for “what is an item payload when it is no longer in memory?” That answer had to be structured enough for templates, opaque enough for locked storage, and portable enough for backup and blind sync.
The design: payload blob inside a sealed box
CBF has two cooperating layers. Naming them in plain language is enough; the important part is the boundary each layer owns.
Layer 1 — the payload blob
While editing, the application works with a template-driven field map: placement keys and values. Before encryption, that map is encoded into a binary payload blob.
The blob is not “JSON with shorter keys.” It records:
- which kind of payload this is (for example a vault item versus a share snapshot);
- which category template the item belongs to;
- field values addressed by stable field indices from a shared registry, not by free-form string keys scattered through ciphertext; and
- optional extensions such as an attachment manifest when a snapshot needs to name related files without embedding their bytes.
Titles and categories used for list filters and full-text search stay outside this blob as deliberate plaintext metadata. Sensitive values—passwords, account numbers, note bodies, TOTP secrets—live only inside the encoded payload that is about to be sealed.
Layer 2 — the sealed box
The payload blob then enters a portable ciphertext container. Conceptually:
- Optionally compress the blob with zstd when a size heuristic says compression is worthwhile.
- Generate a fresh initialization vector.
- Encrypt with AES-GCM under the object's content encryption key.
- Frame the result with versioned header fields: payload kind, key kind, cipher suite, flags (including whether zstd was used), IV, and ciphertext length.
AES-GCM provides confidentiality and integrity for the inner bytes. A truncated or swapped blob should fail authentication, not decode into a plausible wrong item. The IV is stored with the ciphertext; it is not secret, but it must be unique per encryption under that key.
flowchart LR
Fields[Structured fields in RAM] --> Blob[Payload blob]
Blob -->|optional zstd| Plain[Bytes to seal]
Plain -->|AES-GCM| Box[CBF ciphertext box]
Box --> Disk[SQLite / backup / sync]
Where the box lives
For ordinary vault items, the sealed box is what the local database stores as the item's encrypted payload. The same family of bytes can travel inside backup and replica packages without a second “export dialect.” Blind sync can move opaque ciphertext because the edge never needs to understand field indices—only to store and forward bytes.
This sits under the envelope model described earlier in this series. Each item still gets its own content encryption key. The vault key wraps that CEK. CBF is the shape of the payload the CEK encrypts. Attachment files remain a different story: large binary ciphertext lives outside SQLite in BlobStore, while item fields leave RAM as CBF. Metadata and ciphertext placement are separate decisions; both refuse plaintext at rest.
Opening an item reverses the path after unlock:
- Load the sealed box and the wrapped CEK.
- Unwrap the CEK with the vault key.
- Open the box: authenticate, decrypt, decompress if flagged.
- Decode the payload blob into a field map for the UI.
- Keep plaintext only in memory for the active session.
Web Crypto performs the AES-GCM work in the browser. The master password never rides along with the blob. The format is portable; the keys are not.
The trade-off: a format instead of a dump
CBF costs what every durable format costs.
Authors and tools must speak the layout. A hex dump of a sealed box is not a support console. Debugging needs intentional open paths under unlock, not “print the column.” That is inconvenient compared with browsing JSON in a database GUI—and that inconvenience is partly the point.
Field evolution needs a registry. Stable indices and layout versions replace ad-hoc key renames inside ciphertext. Adding a template field is a product release concern: encode and decode must agree. Unknown extensions need explicit rules so older clients fail safely or skip safely instead of inventing meaning.
Compression is optional, not ornamental. zstd before AES-GCM can shrink repetitive text, but it adds a branch: compress or not, set a flag, decompress on open, reject inconsistent envelopes. Tiny payloads may skip compression. The heuristic exists so CPU and size trade honestly; compression is never a substitute for encryption.
Two crypto stories can be confused. Envelope encryption answers key scope. CBF answers payload shape. Collapsing them—“we encrypt JSON with the vault key”—loses both stories. Keeping them separate means more fields on a row (wrapped CEK, IVs, sealed box) and clearer reasoning when something fails.
We accept that bookkeeping because the alternative is worse: a vault that looks encrypted while still thinking in plaintext schemas on disk, or a zoo of channel-specific serializers that drift apart.
What we refuse
We refuse plaintext structured payloads at rest. An unlocked editor may hold a field map in memory. Locked storage holds a sealed box. Refresh, idle lock, and process exit clear working keys; they do not leave a friendly JSON souvenir.
We refuse inventing a new wire shape per channel. Local persistence, backup, and replica transport should carry the same portable ciphertext family for item payloads—not a SQLite dialect, a zip-of-JSON dialect, and a sync dialect that almost agree.
We refuse treating compression as confidentiality. zstd reduces size. AES-GCM provides secrecy and authenticity. Flags record what happened; they do not replace the seal.
We refuse free-form secret keys inside durable ciphertext. Field indices and template context make payloads evolvable without scattering password-shaped string keys through every stored blob.
We refuse asking the edge to interpret fields. Optional sync may store or relay opaque boxes. A blind replica does not need to know which index is a password. If a server must parse your notes to “help,” it is no longer a zero-knowledge boundary.
We refuse one vault-wide content key as a substitute for format discipline. Even with CBF, each object keeps its own CEK. A portable blob under one shared content key would still couple rotation, sharing, and blast radius the wrong way.
Same fields, durable only as ciphertext
A structured vault earns its keep in memory: templates, validation, focused editors, searchable titles outside the seal. It keeps its promise at rest by refusing to leave that structure lying around.
CBF is the hinge between those two modes. Structured fields become a payload blob. The blob becomes a sealed AES-GCM box, optionally compressed first. That box is what survives lock. Envelope encryption decides which key opens which object. Local salt and the refusal of provider password reset decide who can ever derive the vault key in the first place.
For the key hierarchy around each object, read One key per object: envelope encryption inside NT² Vault. For why unlock material never becomes a cloud guessing kit, read The KDF salt stays on your device. For why we cannot reset a master password on purpose, read No password reset, by design. For how large files keep ciphertext out of the relational catalog, read Metadata in SQLite, ciphertext in BlobStore.
If you want a vault whose sensitive fields leave RAM only as portable ciphertext, try NT² Vault or read more at nt2.me.
Last updated 2026-09-23
Related stories
- One key per object: envelope encryption inside NT² Vault
9 min read
- Wrong password must fail closed
8 min read
- Share passphrase ≠ master password
8 min read