CipherStashDocs
Architecture & security

Cryptography

The canonical account of CipherStash's cryptographic design: the key hierarchy, what ZeroKMS does and does not learn, and what is cached.

This page is the single reference for CipherStash's cryptographic design. Every other page that touches key management links here rather than restating it.

Cryptographic primitives

PurposeAlgorithmDetails
Data encryptionAES-256-GCM-SIVAuthenticated encryption with nonce-misuse resistance
Equality search termsHMAC-SHA-256Deterministic terms for exact-match lookups
Range and sorting termsCLLW OPE or block OREEQL defaults to a directly sortable CLLW order-preserving term; block ORE is available as a pinned alternative (Lewi-Wu 2016, Bogatov et al. 2018)
Free-text search termsEncrypted Bloom filtersTrigram tokenization (Nojima/Kadobayashi 2009, Chum/Zhang 2017)
Payload integrityBLAKE3Structure validation of the encrypted payload
Payload encodingMessagePack + Base85Compact binary serialization, stored as jsonb in PostgreSQL

Encryption and decryption happen in your application process, in the native Rust module. CipherStash infrastructure never sees plaintext.

The key hierarchy

Three distinct things are often all called "the key". Keeping them apart is what makes the rest of this page readable.

KeyWhere it livesWhat protects it
Authority keyZeroKMS, server-sideEncrypted at rest under an AWS KMS root key
Client keyYour application or workloadYou do (CS_CLIENT_KEY)
Data keyYour application's memory, brieflyNothing. It is derived per value, identified by a key ID, and discarded

The authority key and the client key are two halves of a split. Neither alone derives a data key, and the two are never brought together in one place. The key seed travels from ZeroKMS to you. The client key never travels at all.

How a data key is produced

The mechanism operates at two layers, and descriptions that name only one of them are incomplete.

Layer 1: the key seed, produced server-side

Your application requests a key seed by sending a key ID. ZeroKMS uses proxy symmetric re-encryption (patent pending) on the authority key to produce the key seed for that ID, and returns it.

The ID is what gives every data key a unique identity. It travels with the encrypted value, so the same seed can be requested again to decrypt it.

ZeroKMS does all of this without the client key. It never receives it, and the request carries no key material of yours, only the ID. That is what makes the architecture zero-knowledge: ZeroKMS produces a seed it cannot itself turn into a data key.

Layer 2: the data key, derived client-side

Your application processes the key seed with the client key, then expands the result into a unique data key per value using an HMAC-based key derivation function. The data key encrypts the value with AES-256-GCM-SIV, then is discarded from memory.

The client key never leaves your infrastructure, and neither does anything it processes. Only the seed crosses the boundary, and it crosses inward.

Re-encryption describes how the seed is produced. HMAC key derivation describes how the processed seed becomes per-value keys. Both are true, at different layers.

Note what crosses the boundary. Outbound: a key ID, which is not key material. Inbound: the key seed. Nothing else leaves your infrastructure, not the client key, not the processed seed, not the data key.

What is cached, and what is not

This distinction matters for a security review, and earlier documentation stated it too broadly. "Nothing is cached" is not accurate. What is true is that no data key is ever cached, stored, or transmitted.

ThingCached?Detail
Data keysNeverDerived per value, held in memory for the duration of one operation, then discarded
Key seedsNot persistedHeld only for the operation that requested them
Keyset-scoped ciphersYes, in ProxyCipherStash Proxy caches keyset-scoped cipher objects so it does not re-initialize per statement
Authority keysAt rest, in ZeroKMSEncrypted under an AWS KMS root key

Proxy's cipher cache is bounded by cipher_cache_size (64 entries) and cipher_cache_ttl_seconds (3600 seconds), and its hit rate is exposed as cipherstash_proxy_keyset_cipher_cache_hits_total. Caching a keyset-scoped cipher is not the same as caching a data key: the cipher still requires the client key to derive anything, and per-value keys are still derived per value.

The Stack SDK derives per operation and caches nothing.

Trust model

An attacker must compromise both the ZeroKMS authority key and your client key to derive data keys. Compromising either alone is insufficient.

  • CipherStash never sees plaintext. Encryption and decryption run in your process.
  • CipherStash never sees your client key, nor anything the client key has processed.
  • CipherStash never sees a data key. Data keys are derived in your memory.
  • Ciphertext never has to leave your infrastructure. ZeroKMS handles key material, not data.

Shared responsibility

YouCipherStash
Protect the client key (CS_CLIENT_KEY)Protect authority keys, encrypted at rest under AWS KMS
Secure your application and databaseOperate ZeroKMS with high availability
Manage access keys and keysetsEnforce access-control policy on keysets
Register identity providers for lock contextsOperate the CipherStash Token Service (CTS)
Store encrypted data in your databaseNever store, access, or log plaintext

Blast radius

Keysets scope keys. Each keyset is a full cryptographic boundary: one tenant's keyset cannot decrypt another's data. Compromising a client key affects only the keysets that application can reach, and revoking its access key stops further key derivation immediately.

Data flow

Write path

  1. The application calls client.encrypt(plaintext, { column, table }).
  2. The SDK requests a key seed from ZeroKMS over TLS, sending a unique key ID. The request carries no key material.
  3. ZeroKMS re-encrypts under the authority key and returns the key seed for that ID.
  4. The SDK processes the seed with the client key, then derives the data key from the result, locally.
  5. The SDK encrypts the plaintext with AES-256-GCM-SIV.
  6. If the column declares searchable capability, the SDK generates the index terms (HMAC, CLLW OPE or block ORE, Bloom filter).
  7. The SDK packs ciphertext and index terms into an EQL payload.
  8. The data key is discarded.
  9. The application stores the payload in PostgreSQL.

Read path

  1. The application reads the payload and calls client.decrypt(...).
  2. The SDK requests a key seed using the key ID stored with the value, processes it with the client key, and derives the data key locally.
  3. The SDK decrypts the ciphertext, discards the data key, and returns plaintext.

Query path

  1. The application encrypts a search term.
  2. The SDK generates the appropriate index term (HMAC for equality, CLLW OPE or block ORE for range and ordering, Bloom filter for free text).
  3. PostgreSQL compares encrypted terms using EQL operators. The database never sees plaintext.

What querying encrypted data reveals

Searchable encryption is a trade. Each index term reveals bounded information to whoever can read the database: HMAC terms reveal equality and therefore frequency, OPE and ORE terms reveal relative order, and Bloom filter terms reveal probabilistic token membership.

That leakage model is documented once, in Searchable encryption, with per-term detail and guidance on when the trade is not worth making. Assess it as part of your threat model. If the ordering or frequency of a column's values is itself sensitive, encrypt that column without a searchable index and filter after decryption.

Network security

All communication between the SDK and CipherStash services uses TLS 1.2 or later:

  • SDK to ZeroKMS, for key seeds.
  • SDK to CTS, for token exchange during identity-aware encryption.
  • SDK to your database: your existing connection. CipherStash does not proxy it.

Key material does not leave the region your workspace is configured in. See data residency.

Open-source components

ComponentRepository
EQLcipherstash/encrypt-query-language
ORE implementationcipherstash/ore.rs
CipherStash Proxycipherstash/proxy

The core cryptographic implementations are open source and independently auditable. ZeroKMS is a managed service operated by CipherStash.

On this page