From 26b175e66be46bdd2f9396add0121231efe97f32 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Thu, 9 Jul 2026 18:11:41 +1000 Subject: [PATCH] docs(security): correct the ZeroKMS key exchange and caching claims The live security architecture page told security reviewers that the client key is transmitted to CipherStash: "The SDK sends a key derivation request to ZeroKMS with the client key component." "ZeroKMS combines the authority key and client key to produce a key seed." That inverts the property the architecture exists to provide. Corrected per product review: - The application requests a seed by sending a key ID. The request carries no key material. The ID is what gives each data key a unique identity, so the same value can be decrypted later by asking for the same ID. - ZeroKMS applies proxy symmetric re-encryption to the authority key alone and returns the key seed. It never receives the client key. - The application processes the seed with the client key, then an HMAC-based KDF expands the result into a per-value data key. Only the seed crosses the boundary, and it crosses inward. Two further claims on the same page were wrong: - "No caching needed" / data keys "never cached". No *data key* is ever cached, but Proxy caches keyset-scoped ciphers (cipher_cache_size, cipher_cache_ttl_seconds). The page now states both. - kms/index.mdx: "Root keys are stored in AWS KMS. ZeroKMS handles key derivation and wrapping." AWS KMS protects the authority key at rest; no per-value data key is wrapped or stored. - kms/keysets.mdx: keysets scope the authority key, not a per-keyset root key from which ZeroKMS derives data keys. Standardises on AES-256-GCM-SIV across the corpus (was split 6/3 with plain AES-256-GCM, including two compliance pages). --- content/stack/cipherstash/kms/index.mdx | 8 +-- content/stack/cipherstash/kms/keysets.mdx | 2 +- .../cipherstash/proxy/getting-started.mdx | 2 +- content/stack/deploy/troubleshooting.mdx | 2 +- content/stack/reference/comparisons/fhe.mdx | 4 +- .../stack/reference/security-architecture.mdx | 50 +++++++++++-------- .../stack/reference/use-cases/compliance.mdx | 4 +- 7 files changed, 41 insertions(+), 31 deletions(-) diff --git a/content/stack/cipherstash/kms/index.mdx b/content/stack/cipherstash/kms/index.mdx index f13b772..34efd18 100644 --- a/content/stack/cipherstash/kms/index.mdx +++ b/content/stack/cipherstash/kms/index.mdx @@ -9,13 +9,13 @@ ZeroKMS is the key management layer that powers [Encryption](/stack/cipherstash/ ## Zero-knowledge by design -Existing key management solutions reveal either data or keys to intermediaries. ZeroKMS uses proxy symmetric re-encryption (patent pending). Key seeds are returned to the application. The application creates data keys locally. Data keys are never seen by third parties. Never sent across the network. +Existing key management solutions reveal either data or keys to intermediaries. ZeroKMS uses proxy symmetric re-encryption (patent pending) on its authority key to produce a key seed, which it returns to the application. The application processes that seed with its own client key and derives the data key locally. Data keys are never seen by third parties, and never sent across the network. Neither is the client key. ## How it works -- **Unique key per value:** Each encrypted field uses a distinct data encryption key, not a shared table-level key. -- **AWS KMS backed:** Root keys are stored in AWS KMS. ZeroKMS handles key derivation and wrapping. -- **Zero-knowledge:** CipherStash never sees your plaintext data or unwrapped keys. When a data key is requested, ZeroKMS generates and returns key seeds to the application to create the data key locally. Data keys are never seen by third parties and are never sent across the network. +- **Unique key per value:** Each encrypted field uses a distinct data encryption key, not a shared table-level key. The application requests a seed by key ID, which is what gives each data key its identity. +- **AWS KMS backed:** The authority key is encrypted at rest under an AWS KMS root key. No per-value data key is ever wrapped or stored. +- **Zero-knowledge:** CipherStash never sees your plaintext data, your client key, or an unwrapped data key. ZeroKMS returns a key seed; only your application can turn that seed into a data key, because only your application holds the client key. - **Multi-tenant isolation:** Use keysets to isolate encryption keys per tenant, customer, or business unit. - **Bulk operations:** ZeroKMS supports bulk encryption and decryption operations, enabling a unique data key per record without sacrificing performance. - **Multi-region:** ZeroKMS is highly available and deployed in [multiple cloud regions globally](/stack/cipherstash/kms/regions). It can also be deployed within your own cloud account or on-prem. diff --git a/content/stack/cipherstash/kms/keysets.mdx b/content/stack/cipherstash/kms/keysets.mdx index 35fae60..645c0b3 100644 --- a/content/stack/cipherstash/kms/keysets.mdx +++ b/content/stack/cipherstash/kms/keysets.mdx @@ -49,7 +49,7 @@ Secrets environments map directly to keysets. When you specify an `environment`, ## How it works -When you specify a keyset, ZeroKMS derives all data encryption keys from that keyset's root key. +When you specify a keyset, every key seed ZeroKMS returns is scoped to that keyset's authority key, so the data keys your application derives are scoped to it too. This means: - Data encrypted under keyset A cannot be decrypted with keyset B. diff --git a/content/stack/cipherstash/proxy/getting-started.mdx b/content/stack/cipherstash/proxy/getting-started.mdx index 78d0a05..29fe35e 100644 --- a/content/stack/cipherstash/proxy/getting-started.mdx +++ b/content/stack/cipherstash/proxy/getting-started.mdx @@ -134,5 +134,5 @@ All comparison operations are evaluated against **encrypted** data. The literal CipherStash Proxy provides: - Completely transparent encryption of sensitive data in PostgreSQL -- All data remains searchable, protected with non-deterministic AES-256-GCM encryption +- All data remains searchable, protected with non-deterministic AES-256-GCM-SIV encryption - Zero changes required to your application's database queries diff --git a/content/stack/deploy/troubleshooting.mdx b/content/stack/deploy/troubleshooting.mdx index 1042ff9..9ca8de2 100644 --- a/content/stack/deploy/troubleshooting.mdx +++ b/content/stack/deploy/troubleshooting.mdx @@ -77,7 +77,7 @@ DecryptionError: Lock context mismatch ### Corrupted or tampered ciphertext -If the stored ciphertext has been modified, decryption fails. AES-GCM-SIV provides authenticated encryption, which detects tampering. +If the stored ciphertext has been modified, decryption fails. AES-256-GCM-SIV provides authenticated encryption, which detects tampering. **Fix**: Check that no database triggers, application code, or migrations have modified the encrypted column values. diff --git a/content/stack/reference/comparisons/fhe.mdx b/content/stack/reference/comparisons/fhe.mdx index e2c9b32..e6dac9f 100644 --- a/content/stack/reference/comparisons/fhe.mdx +++ b/content/stack/reference/comparisons/fhe.mdx @@ -67,7 +67,7 @@ In practice, only the 3–5 sensitive columns per table use these schemes. Forei | **Encrypted Bloom filter** | Token / substring containment (`LIKE`, full-text) | Trigram tokens hashed into a fixed-size bit vector; queried via GIN containment with client-side false-positive filtering. | Tuneable false-positive rate doubles as a security knob — higher FPR obscures token-frequency patterns at the cost of more client-side filtering. | | **Block ORE — Lewi-Wu (2016)** | Range and ordering (`<`, `>`, `BETWEEN`, `ORDER BY`) | Two-component ciphertexts (left / right). Right ciphertexts are stored; left ciphertexts are query-time only. Comparison is performed via the Postgres operator class. | Semantically-secure right-ciphertexts. Because left ciphertexts are not stored, **static order is not leaked** — only the order revealed by the queries you actually run. | -Underlying every encrypted value, AES-GCM-SIV provides the authenticated encryption used for retrieval. The schemes above are *searchable indexes* alongside the ciphertext, not replacements for it. The database stores `ciphertext + indexes`; the application receives plaintext only after key-bound decryption. +Underlying every encrypted value, AES-256-GCM-SIV provides the authenticated encryption used for retrieval. The schemes above are *searchable indexes* alongside the ciphertext, not replacements for it. The database stores `ciphertext + indexes`; the application receives plaintext only after key-bound decryption. For full leakage definitions and parameter-selection guidance, see [Security architecture](/stack/reference/security-architecture). @@ -103,7 +103,7 @@ Two architectural consequences are worth calling out: **Schemes are swappable.** A column's index can be re-keyed or migrated to a different scheme without changing application SQL. As stronger primitives mature — lattice-based searchable schemes, structured encryption variants, eventually practical FHE for specific operations — they slot in behind the same EQL surface. -**Quantum-resistant by construction.** The current primitives (AES-GCM-SIV, HMAC-SHA-256, BLAKE3) avoid the cryptographic assumptions known to fall to a sufficiently large quantum computer. No RSA, no elliptic curves. A column encrypted today is still secure on the day post-quantum migration becomes urgent. +**Quantum-resistant by construction.** The current primitives (AES-256-GCM-SIV, HMAC-SHA-256, BLAKE3) avoid the cryptographic assumptions known to fall to a sufficiently large quantum computer. No RSA, no elliptic curves. A column encrypted today is still secure on the day post-quantum migration becomes urgent. ## What this looks like in SQL diff --git a/content/stack/reference/security-architecture.mdx b/content/stack/reference/security-architecture.mdx index 351f4ef..eddedef 100644 --- a/content/stack/reference/security-architecture.mdx +++ b/content/stack/reference/security-architecture.mdx @@ -9,7 +9,7 @@ This page is the single reference for CipherStash's cryptographic design. It cov | Purpose | Algorithm | Details | |---|---|---| -| Data encryption | AES-GCM-SIV | 256-bit keys, authenticated encryption with nonce-misuse resistance | +| Data encryption | AES-256-GCM-SIV | 256-bit keys, authenticated encryption with nonce-misuse resistance | | Equality search tokens | HMAC-SHA-256 | Deterministic tokens for exact-match lookups | | Range and sorting indexes | Block ORE | Order-Revealing Encryption ([Lewi-Wu 2016](https://eprint.iacr.org/2016/612)) with security enhancements from [Bogatov et al 2018](https://arxiv.org/abs/1810.05135) | | Free-text search indexes | Encrypted Bloom filters | Trigram-based tokenization ([Nojima/Kadobayashi 2009](https://link.springer.com/chapter/10.1007/978-3-642-04474-8_17), [Chum/Zhang 2017](https://link.springer.com/chapter/10.1007/978-3-319-66399-9_15)) | @@ -24,37 +24,47 @@ CipherStash uses a **distributed trust model** with a dual-party key split. No s ### Key types -| Key | Where it lives | Purpose | +| Key | Where it lives | Protected by | |---|---|---| -| **Authority key** | ZeroKMS (server-side, HSM-friendly) | Server half of the key derivation input | -| **Client key** | Your application / workload | Client half of the key derivation input (`CS_CLIENT_KEY` env var) | -| **Data key** | Ephemeral (in-memory only) | Per-value encryption key, derived from both halves, never stored | +| **Authority key** | ZeroKMS (server-side) | Encrypted at rest under an AWS KMS root key | +| **Client key** | Your application / workload | You (`CS_CLIENT_KEY` env var). It never leaves your infrastructure | +| **Data key** | Ephemeral (in-memory only) | Nothing. Derived per value, then discarded | ### Key derivation -ZeroKMS uses an **HMAC-based key derivation scheme**: +The mechanism operates at two layers. Descriptions that name only one of them are incomplete. -1. The SDK sends a key derivation request to ZeroKMS with the client key component. -2. ZeroKMS combines the authority key and client key to produce a **key seed**. -3. The SDK uses the key seed to derive a unique data encryption key locally. -4. The data key encrypts the value using AES-GCM-SIV, then is immediately discarded. +**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. -Data keys are **ephemeral**: they are never stored, cached, or transmitted. This eliminates wrapped-key storage overhead and removes the risk of key cache compromise. +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](/stack/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 KMS | CipherStash ZeroKMS | |---|---| | Wrapped data keys stored alongside data | Data keys are ephemeral (never stored) | -| Key caching required for performance | No caching needed. HMAC derivation at 10,000+ ops/sec per node | +| Data keys cached to make performance tolerable | No 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 keys | Compromising ZeroKMS alone is insufficient. Attacker also needs client key | +| Compromising KMS exposes all data keys | Compromising 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. @@ -81,10 +91,10 @@ CipherStash uses [keysets](/stack/cipherstash/kms/keysets) to scope encryption k ### Encryption (write path) 1. Application calls `client.encrypt(plaintext, { column, table })`. -2. SDK sends a key derivation request to ZeroKMS (over TLS). -3. ZeroKMS combines authority key + client key → returns key seed. -4. SDK derives a unique data key from the seed locally. -5. SDK encrypts the plaintext using AES-GCM-SIV with the data key. +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](/stack/reference/cipher-cell) (JSON payload). 8. Data key is discarded from memory. @@ -94,9 +104,9 @@ CipherStash uses [keysets](/stack/cipherstash/kms/keysets) to scope encryption k 1. Application reads the CipherCell from PostgreSQL. 2. Application calls `client.decrypt(encryptedData)`. -3. SDK sends a key derivation request to ZeroKMS. -4. ZeroKMS combines authority key + client key → returns key seed. -5. SDK derives the data key and decrypts the ciphertext. +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. diff --git a/content/stack/reference/use-cases/compliance.mdx b/content/stack/reference/use-cases/compliance.mdx index ba582e0..5a44b10 100644 --- a/content/stack/reference/use-cases/compliance.mdx +++ b/content/stack/reference/use-cases/compliance.mdx @@ -88,7 +88,7 @@ Every encryption and decryption operation through ZeroKMS produces an audit even ### Integrity controls (§ 164.312(c)) -CipherStash's authenticated encryption (AES-256-GCM) detects any tampering with encrypted data during decryption. The operation fails if the ciphertext has been modified. +CipherStash's authenticated encryption (AES-256-GCM-SIV) detects any tampering with encrypted data during decryption. The operation fails if the ciphertext has been modified. ## PCI-DSS compliance patterns @@ -119,7 +119,7 @@ CipherStash's audit logging provides cryptographic proof of data access. Every d | Regulation | Key requirements | CipherStash features | |---|---|---| | **GDPR** | Encryption, data minimization, right to erasure | Searchable encryption, Lock Contexts, crypto-shredding | -| **HIPAA** | Access controls, audit trails, integrity | Identity-aware encryption, ZeroKMS audit logs, AES-256-GCM | +| **HIPAA** | Access controls, audit trails, integrity | Identity-aware encryption, ZeroKMS audit logs, AES-256-GCM-SIV | | **PCI-DSS** | Protect cardholder data, access monitoring | Application-layer encryption, audit logging | | **SOC2** | Encryption at rest and in use, access controls | Encryption-in-use, role-based key access | | **ISO 27001** | Information security management | End-to-end encryption, key management, audit trails |