CipherStashDocs

Security architecture

Understand the CipherStash security architecture, covering cryptographic primitives, the ZeroKMS key hierarchy, zero-knowledge trust model, and data flow.

This page is the single reference for CipherStash's cryptographic design. It covers the key hierarchy, encryption primitives, trust model, and data flow so security teams can evaluate CipherStash without piecing together information from multiple sources.

Cryptographic primitives

PurposeAlgorithmDetails
Data encryptionAES-256-GCM-SIV256-bit keys, authenticated encryption with nonce-misuse resistance
Equality search tokensHMAC-SHA-256Deterministic tokens for exact-match lookups
Range and sorting indexesBlock OREOrder-Revealing Encryption (Lewi-Wu 2016) with security enhancements from Bogatov et al 2018
Free-text search indexesEncrypted Bloom filtersTrigram-based tokenization (Nojima/Kadobayashi 2009, Chum/Zhang 2017)
Ciphertext integrityBlake3Fast cryptographic hash for CipherCell structure validation
Ciphertext encodingMessagePack + Base85Compact binary serialization stored as JSONB in PostgreSQL

All encryption happens client-side in the application process. CipherStash infrastructure never sees plaintext data.

Key hierarchy

CipherStash uses a distributed trust model with a dual-party key split. No single party (including CipherStash) can derive data encryption keys alone.

Key types

KeyWhere it livesProtected by
Authority keyZeroKMS (server-side)Encrypted at rest under an AWS KMS root key
Client keyYour application / workloadYou (CS_CLIENT_KEY env var). It never leaves your infrastructure
Data keyEphemeral (in-memory only)Nothing. Derived per value, then discarded

Key derivation

The mechanism operates at two layers. Descriptions that name only one of them are incomplete.

Layer 1, server-side. Your application requests a key seed by sending a key ID. ZeroKMS applies proxy symmetric re-encryption (patent pending) to the authority key and returns the key seed for that ID. It does this without the client key: ZeroKMS never receives it, and the request carries no key material of yours, only the ID.

The ID is what gives every data key a unique identity, so a value can be decrypted later by requesting the seed for the same ID.

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

Only the key seed crosses the boundary, and it crosses inward. Nothing crosses outward: not the client key, not the processed seed, not the data key.

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.

What is cached, and what is not

Data keys are never stored, cached, or transmitted. They are derived per value, held for one operation, and discarded. This eliminates wrapped-key storage overhead.

CipherStash Proxy does cache keyset-scoped cipher objects, bounded by cipher_cache_size (64 entries) and cipher_cache_ttl_seconds (3600 seconds), with hit rates exposed as cipherstash_proxy_keyset_cipher_cache_hits_total. That 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 Encryption SDK derives per operation and caches nothing.

Why this matters

Traditional KMSCipherStash ZeroKMS
Wrapped data keys stored alongside dataData keys are ephemeral (never stored)
Data keys cached to make performance tolerableNo data key is ever cached, at any layer
Single point of trust (KMS provider)Distributed trust: both authority key and client key required
Compromising KMS exposes all data keysCompromising ZeroKMS alone is insufficient. An attacker also needs the client key, which ZeroKMS never sees

Trust model

CipherStash implements a zero-knowledge architecture:

  • CipherStash never sees plaintext data. All encryption and decryption happens in your application using the native Rust FFI module.
  • CipherStash never sees your client key, nor anything the client key has processed.
  • CipherStash never sees unwrapped data keys. Data keys are derived ephemerally and exist only in the client's memory.
  • Both parties are required. An attacker must compromise both the ZeroKMS authority key and the client key to derive data keys.

Shared responsibility

Your responsibilityCipherStash responsibility
Protect the client key (CS_CLIENT_KEY)Protect authority keys in ZeroKMS (HSM-backed)
Secure your application and databaseOperate ZeroKMS infrastructure with high availability
Manage access keys and keysetsEnforce access control policies on keysets
Configure identity providers for lock contextsOperate the CipherStash Token Service (CTS)
Store encrypted data in your databaseNever store, access, or log plaintext data

Blast radius

CipherStash uses keysets to scope encryption keys:

  • Each keyset provides full cryptographic isolation. Tenant A's keyset cannot decrypt Tenant B's data.
  • Compromising a single client key affects only the keysets that application has access to.
  • Revoking an application's access key immediately prevents further key derivation requests.

Data flow

Encryption (write path)

  1. Application calls client.encrypt(plaintext, { column, table }).
  2. 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 → returns the key seed for that ID.
  4. SDK processes the seed with the client key, then derives the data key from the result, locally.
  5. SDK encrypts the plaintext using AES-256-GCM-SIV with the data key.
  6. SDK generates search index tokens (HMAC, ORE, Bloom filter) if the column has indexes configured.
  7. SDK packages ciphertext + index tokens into a CipherCell (JSON payload).
  8. Data key is discarded from memory.
  9. Application stores the CipherCell in PostgreSQL (as eql_v2_encrypted or jsonb).

Decryption (read path)

  1. Application reads the CipherCell from PostgreSQL.
  2. Application calls client.decrypt(encryptedData).
  3. SDK requests a key seed from ZeroKMS, sending the key ID stored with the value.
  4. ZeroKMS re-encrypts under the authority key → returns the key seed for that ID.
  5. SDK processes the seed with the client key, derives the data key, and decrypts the ciphertext.
  6. Data key is discarded from memory.
  7. Plaintext is returned to the application.

Search (query path)

  1. Application calls client.encryptQuery(searchTerm, { column, table, queryType }).
  2. SDK generates search tokens (HMAC for equality, ORE for range, Bloom filter for free-text) using a key derived from ZeroKMS.
  3. Application sends the encrypted tokens to PostgreSQL.
  4. PostgreSQL's EQL extension compares encrypted tokens using the appropriate index. The database never sees plaintext.

Searchable encryption security properties

Searchable encryption enables queries on encrypted data but involves inherent security tradeoffs. Each index type reveals specific, bounded information to the database server.

Index typeWhat is revealed to the databaseMitigation
HMAC (equality)Whether two encrypted values are identical (frequency information)Per-column unique keys prevent cross-column correlation
ORE (range/sort)Relative ordering of encrypted valuesBlock ORE limits leakage to block-level ordering (Lewi-Wu 2016)
Bloom filter (free-text)Probabilistic token membership with configurable false positive rateParameters k and m control the tradeoff between search accuracy and information leakage

When to use searchable encryption

  • Use it when you need to query encrypted data and the leakage profile is acceptable for your threat model.
  • Don't use it when the mere ordering or frequency of values is sensitive (e.g., exact salary rankings). In those cases, encrypt without indexes and decrypt application-side.

For a deeper analysis of each index type, see Searchable encryption and Supported queries.

Network security

All communication between the SDK and CipherStash services uses TLS 1.2+ over HTTPS:

  • SDK → ZeroKMS: Key derivation requests over HTTPS
  • SDK → CTS: Token exchange requests over HTTPS (for identity-aware encryption)
  • SDK → your database: Your existing database connection (CipherStash does not proxy this)

CipherStash services are deployed across multiple AWS regions. Key material does not leave the region your workspace is configured in.

Open-source components

ComponentRepositoryLicense
EQL (Encrypt Query Language)github.com/cipherstash/encrypt-query-languageOpen source
ORE implementationgithub.com/cipherstash/ore.rsOpen source
CipherStash Proxygithub.com/cipherstash/proxyOpen source

The core cryptographic implementations (ORE, Bloom filter construction) are open source and independently auditable. ZeroKMS is a managed service operated by CipherStash.

Further reading

On this page