diff --git a/IA.md b/IA.md index 6f51750..8ba7d33 100644 --- a/IA.md +++ b/IA.md @@ -141,7 +141,6 @@ Concepts ├─ Searchable encryption canonical leakage model ├─ EQL typed-column model ├─ Key management -├─ Identity-aware encryption ├─ Threat modelling └─ Compare aws-kms · fhe · rls-and-tde · hashicorp-vault Guides @@ -198,7 +197,7 @@ layer, not a layer every Stack page sits on. ciphertext queryable there — but Stack also encrypts general-purpose values and non-Postgres stores (e.g. DynamoDB) with no EQL at all. - Start here: Quickstart · Reference → Stack SDK (client + configuration) -- Concepts: application-level encryption · searchable encryption · identity-aware encryption +- Concepts: application-level encryption · searchable encryption · key management - Guides: schema design · encrypt existing data · testing & CI · serverless & bundling - Reference: `/reference/stack/*` (overview · usage · generated package API) - Security: `/security/stack-sdk` @@ -232,7 +231,7 @@ layer, not a layer every Stack page sits on. - Locator: the base everything relies on — key management (ZeroKMS) and identity-bound access (CTS). - Start here: Security → Architecture · Concepts → Key management -- Concepts: key management · identity-aware encryption +- Concepts: key management - Reference: `/reference/auth/*` (lock-contexts · cts-tokens · oidc · access-keys) - Security: architecture · zerokms · cts · availability · audit logging · key ownership - Integrations (auto): Clerk · Auth0 · Okta @@ -261,8 +260,8 @@ queries, not by a subtree: - The Platform hub gathers all of it — everything above carries `platform`. - Faceted search on `integration.category: auth-provider` gives the providers. -- `/concepts/identity-aware-encryption` is the one explanatory page that ties the - concept together in prose; everything else links to it (anti-drift rule). +- `/solutions/provable-access` is the explanatory page that ties identity-bound + access together in prose; everything else links to it (anti-drift rule). Naming caution: `/reference/auth/*` means the CTS _service_; the `@cipherstash/auth` _package_ lives at `/reference/stack/auth`. Two different things both called @@ -325,10 +324,9 @@ Two notes: - [x] Section scaffold 🚧 - [ ] `/concepts/privacy-first-design` - [ ] `/concepts/application-level-encryption` — vs TDE/pgcrypto/RLS -- [ ] `/concepts/searchable-encryption` — REWRITE with honest leakage model (canonical leakage page) +- [x] `/concepts/searchable-encryption` — REWRITE with honest leakage model (canonical leakage page) - [ ] `/concepts/eql` — the typed-column model (declare capability in the schema) -- [ ] `/concepts/key-management` — per-value keys, rotation, crypto-shredding -- [ ] `/concepts/identity-aware-encryption` — lock contexts, CTS (CIP-3330) +- [x] `/concepts/key-management` — per-value keys, split control, revocation, isolation, and audit - [ ] `/concepts/threat-modelling` ## Comparisons — CIP-3333 diff --git a/content/docs/concepts/identity-aware-encryption.mdx b/content/docs/concepts/identity-aware-encryption.mdx deleted file mode 100644 index 3f11530..0000000 --- a/content/docs/concepts/identity-aware-encryption.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Identity-aware encryption -description: "Lock contexts and CTS: binding encrypted values to a user identity so only that identity can decrypt them." -type: concept ---- - -This page is being built as part of the docs V2 overhaul ([CIP-3330](https://linear.app/cipherstash/issue/CIP-3330)). Track progress in [IA.md](https://github.com/cipherstash/docs/blob/v2/IA.md). - -Until it lands, the current version lives in the [existing docs](/stack/cipherstash/encryption/identity). diff --git a/content/docs/concepts/index.mdx b/content/docs/concepts/index.mdx index 8fc5128..922af97 100644 --- a/content/docs/concepts/index.mdx +++ b/content/docs/concepts/index.mdx @@ -1,10 +1,11 @@ --- title: Concepts -navTitle: Overview -description: "How CipherStash works and how to think about searchable encryption, keys, and identity." +description: "How CipherStash keeps encrypted data useful and controls access to plaintext." type: concept --- -This section is being built as part of the docs V2 overhaul ([CIP-3307](https://linear.app/cipherstash/issue/CIP-3307)). Track progress in [IA.md](https://github.com/cipherstash/docs/blob/v2/IA.md). - -Until it lands, current documentation lives in the [existing docs](/stack). +{/* + Fumadocs requires an index document when generating a metadata-backed folder. + meta.json excludes this file from the page tree, and Next redirects its route + to the first visible Concepts page. +*/} diff --git a/content/docs/concepts/key-management.mdx b/content/docs/concepts/key-management.mdx new file mode 100644 index 0000000..136897f --- /dev/null +++ b/content/docs/concepts/key-management.mdx @@ -0,0 +1,176 @@ +--- +title: Key management +description: "How ZeroKMS makes unique per-value keys practical: split control, client-side derivation, bounded compromise, immediate revocation, and batch performance without data-key caching." +type: concept +components: [encryption, platform] +audience: [developer, cto, ciso] +verifiedAgainst: + stack: "1.0.0" +--- + +Encryption is only as strong as the system that controls its keys. If an application keeps one long-lived key beside the data it protects, encrypting the database changes its appearance without meaningfully changing who can unlock it. + +ZeroKMS takes a different approach. Every encrypted value gets a unique data key. No data key is stored, transmitted, or reused, and neither CipherStash nor your application holds enough long-lived material to derive one alone. + +This gives CipherStash three properties that are difficult to combine with conventional key management: + +- **Fine-grained protection:** compromising one derived data key exposes one value, not a table or batch. +- **Split control:** CipherStash holds one side of the key relationship and your application holds the other. Neither side is sufficient by itself. +- **Production performance:** bulk operations derive unique keys locally after one ZeroKMS interaction instead of making one network request per value. + +## The problem with conventional envelope encryption + +Cloud key-management services commonly use envelope encryption. The KMS generates a data encryption key, returns the plaintext key to the application, and returns a second copy wrapped by a root key. The application encrypts the data, discards the plaintext key, and stores the wrapped key beside the ciphertext. + +On read, the application sends the wrapped key back to the KMS and receives the plaintext data key again. + +That works well for individual objects. It becomes difficult at database scale. A result containing 100 rows with two independently encrypted fields needs 200 data keys. Using one unique key per value means hundreds of KMS requests; reusing and caching a key makes the application faster but expands the amount of data that key can decrypt. + +Caching also weakens observability. Once one cached key unlocks thousands of values, the key service can record that the key was used but cannot tell which individual value the application accessed. + +The usual design therefore trades among: + +- how much data one key can decrypt; +- how many network calls an operation can tolerate; and +- how precisely key use can be audited. + +ZeroKMS is designed to remove that tradeoff. + +## The ZeroKMS mental model + +A usable data key is never handed from one system to another. It comes into existence only inside your application, when material from ZeroKMS is processed with material that never leaves your environment. + +Four pieces have distinct jobs: + +| Material | Where it exists | What it can do alone | +|---|---|---| +| **Authority key** | ZeroKMS, encrypted at rest under an AWS KMS root key | Produce a key seed; it cannot derive your data keys without the client key | +| **Client key** | Your application environment | Process a key seed; it cannot derive a data key without ZeroKMS | +| **Key seed** | Returned by ZeroKMS for a key ID and held for the operation | Nothing useful without the client key | +| **Data key** | Your application's memory for one operation | Encrypt or decrypt one value; then it is discarded | + +The authority key and client key form a split. They are never brought together. ZeroKMS never receives the client key, and your application never receives the authority key. + +```mermaid +flowchart LR + keyid["Key ID"] --> zerokms["ZeroKMS
authority key"] + zerokms --> seed["Key seed"] + + subgraph app["Your application"] + client["Client key
never leaves"] + combine["Process seed + derive"] + datakey["Unique data key
one value, one operation"] + value["Encrypt or decrypt value"] + discard["Discard data key"] + + client --> combine + combine --> datakey --> value --> discard + end + + seed --> combine +``` + +CipherStash uses proxy symmetric re-encryption to produce the seed on the ZeroKMS side. The application then processes that seed with its client key and uses an HMAC-based key derivation function to produce the data key. [Cryptography](/security/cryptography) is the canonical technical description of both layers. + +## A unique key for every value + +Each encrypted value carries a key ID. That ID is not secret and is not key material; it tells ZeroKMS which reproducible seed the client needs for that value. + +### Encrypt + +1. The client creates a new key ID. +2. ZeroKMS authorizes the request and produces the corresponding key seed. +3. The application processes the seed with its client key and derives a unique data key. +4. The data key encrypts one value, then is discarded. +5. The key ID travels with the ciphertext. The data key does not. + +### Decrypt + +1. The client reads the key ID from the encrypted payload. +2. ZeroKMS authorizes a request for that ID and reproduces the same seed. +3. The application processes it with the client key and derives the same data key. +4. The value is decrypted and the data key is discarded again. + +There is no database of per-value data keys to protect or restore. The persistent state is the much smaller authority/client relationship from which a data key can be reproduced only when both sides participate. + +## Why split control changes application compromise + +A compromised application is serious, but it is not equivalent to losing a permanent master key. + +**The client key is insufficient on its own.** Stealing it does not let an attacker derive historical data keys offline. The encrypted payload contains a key ID, not a seed or wrapped data key. ZeroKMS must still authorize and participate in each derivation. + +**There is no cache of reusable data keys to steal.** A data key exists only while one value is being processed. Capturing it does not unlock the next row. + +**Access has a server-side off switch.** Revoking a client's access key or its grant to a keyset prevents future seed requests. Because the decision is made during derivation, revocation does not depend on a compromised process voluntarily honoring a new policy. + +**The blast radius is scoped.** A client can derive keys only for the keysets it has been granted. Separate keysets create cryptographic boundaries between tenants, environments, regions, or workloads. + +**Identity can narrow it further.** A lock context can bind derivation to a claim from an authenticated end user. Possessing the service's client key is then not enough to decrypt a value locked to a different identity. See [Provable access control](/solutions/provable-access). + +**Derivation is observed outside the application.** ZeroKMS records the operation before returning the material required for decryption. An attacker operating only inside the application cannot edit or suppress that service-side record in the way they could alter application logs. + +An attacker controlling a live, authorized process may still request the operations available to that process while access remains valid. The important difference is that the capability is **scoped, observable, and revocable**—not a reusable master key that can be copied once and used forever offline. + +## Keysets define the isolation boundary + +A keyset is an independent authority-key scope. The same key ID under two keysets produces unrelated data keys, so ciphertext created under one keyset cannot be decrypted through another. + +Common boundaries include: + +- one keyset per customer in a multi-tenant application; +- separate keysets for development, staging, and production; +- regional keysets for data-residency requirements; and +- separate keysets for business units or workloads with different operators. + +Client access is granted per keyset. Revoking that grant stops the client from deriving any more keys in that boundary without changing database permissions or rewriting the ciphertext. + +Keyset design also controls intentional correlation. Encrypted equality and joins work only across compatible values under the same cryptographic scope; separating keysets prevents both decryption and cross-boundary encrypted comparison. + +The default workspace keyset is sufficient for many applications. Add more when your security boundary is narrower than the workspace. + +## Bulk performance without data-key caching + +The data-key-per-value model would be impractical if every value needed an independent network and HSM round trip. + +ZeroKMS batches the server-side work. A bulk operation can obtain the seed material it needs in one interaction, then derive each value's unique data key locally. Increasing a batch from ten values to a thousand increases local cryptographic work, but does not turn into a thousand sequential requests to the key service. + +This is why the Stack SDK's bulk operations are both the performance path and the strongest key-granularity path. They do not improve throughput by reusing a data key. + + +No **data key** is cached. CipherStash Proxy does cache bounded, keyset-scoped cipher objects so it does not reinitialize them for every statement. Those objects still require the client side of the split, and every value still receives its own derived data key. See [what is cached](/security/cryptography#what-is-cached-and-what-is-not). + + +## Authorization and audit are part of derivation + +With a policy check in application code, authorization happens first and decryption happens later. A bug or compromised process can create a gap between the decision and the use of the key. + +ZeroKMS evaluates authorization while producing the key seed. The authorization decision and the cryptographic operation are therefore part of the same event: if ZeroKMS does not authorize the request, the application does not receive the material required to derive the data key. + +That event also creates an independent audit record. Depending on how the client authenticated, it can identify the service or federated end user, the keyset, the operation, and the time. Lock contexts add the identity claim bound to the value. + +This is more useful than a log that says a SQL query ran. It records the operation that made plaintext recovery possible, at the system that had to participate in it. + +## What is stored + +The design deliberately keeps the durable pieces small and separated: + +| Location | Stored | +|---|---| +| Your database | Ciphertext, key ID, and any searchable index terms | +| Your application environment | Client key and credentials used to authenticate to ZeroKMS | +| ZeroKMS | Authority keys and access-control state | +| Nowhere | Plaintext data keys | + +Your encrypted data stays in your database. ZeroKMS handles key material, not application data. This separation also shapes recovery: restoring ZeroKMS restores the ability to reproduce keys; it does not require CipherStash to restore or move your encrypted database. See [Disaster recovery](/stack/cipherstash/kms/disaster-recovery). + +## ZeroKMS and searchable encryption + +ZeroKMS protects the keys used by the [Stack SDK](/reference/stack) and [CipherStash Proxy](/reference/proxy). Searchable encryption adds purpose-built terms so PostgreSQL can locate ciphertext without those keys. + +The responsibilities remain separate: + +- ZeroKMS participates in encryption and decryption key derivation. +- The client creates ciphertext and searchable terms in your environment. +- PostgreSQL compares terms but cannot derive a data key or decrypt a result. + +Start with [Searchable encryption](/concepts/searchable-encryption) for the query model. Continue to [Cryptography](/security/cryptography) for algorithms, trust boundaries, and the precise data flow. diff --git a/content/docs/concepts/meta.json b/content/docs/concepts/meta.json index a5e37c2..a2add10 100644 --- a/content/docs/concepts/meta.json +++ b/content/docs/concepts/meta.json @@ -1,5 +1,11 @@ { "title": "Concepts", "icon": "Lightbulb", - "pages": ["index", "...", "compare"] + "pages": [ + "!index", + "searchable-encryption", + "key-management", + "...", + "compare" + ] } diff --git a/content/docs/concepts/searchable-encryption.mdx b/content/docs/concepts/searchable-encryption.mdx index 689d1fd..ed6bee3 100644 --- a/content/docs/concepts/searchable-encryption.mdx +++ b/content/docs/concepts/searchable-encryption.mdx @@ -1,9 +1,207 @@ --- title: Searchable encryption -description: "How querying encrypted data works, and exactly what each index term reveals." +description: "How CipherStash keeps encrypted data useful: query capabilities, the write and query flow, PostgreSQL indexes, and the security tradeoffs." type: concept +components: [encryption, eql] +audience: [developer, cto, ciso] +verifiedAgainst: + stack: "1.0.0" + eql: "3.0.4" --- -This page is being rewritten as part of the docs V2 overhaul ([CIP-3333](https://linear.app/cipherstash/issue/CIP-3333)). Track progress in [IA.md](https://github.com/cipherstash/docs/blob/v2/IA.md). +Searchable encryption lets you encrypt sensitive data before it reaches PostgreSQL **without giving up the queries your application depends on**. -Until it lands, the current version lives in the [existing docs](/stack/cipherstash/encryption/searchable-encryption). +Your application can still look up a customer by email, order transactions by date, filter records by a range, match text, and query structured JSON. PostgreSQL answers those queries using encrypted indexes. It never receives the plaintext or the keys that decrypt it. + +That changes field-level encryption from an archival control into something you can use on live application data. + +## What it makes possible + +**A database breach yields ciphertext.** Backups, replicas, database consoles, and direct SQL access no longer expose the protected fields. Encryption happens in your application, outside the database trust boundary. + +**Your product keeps working.** `WHERE`, ranges, ordering, grouping, joins, fuzzy text matching, and JSON containment remain available. Existing PostgreSQL indexes continue to do the heavy lifting. + +**Search does not grant decryption.** PostgreSQL can find matching rows but returns ciphertext. Decryption is a separate operation performed by an authorized application, and can be bound to an end-user identity with [provable access control](/solutions/provable-access). + +**You decrypt less data.** The database narrows millions of encrypted rows to the small result set the application asked for. Only those returned values need to be decrypted. + +**Sensitive access becomes auditable.** Every decryption requires key derivation through ZeroKMS. When the client authenticates as the end user, the audit trail records whose identity authorized that operation—not merely which SQL statement ran. + +These properties are especially useful for applications and AI agents that need to locate sensitive records without receiving blanket access to every value in the table. + +## Why ordinary encryption breaks queries + +Modern authenticated encryption is randomized. Encrypting `ava@example.com` twice produces different ciphertexts, even with the same key. This prevents an observer from recognizing repeated plaintext, but it also means ordinary database operations stop working: + +```sql +WHERE email = 'ava@example.com' +ORDER BY date_of_birth +WHERE name LIKE '%martin%' +``` + +Two ciphertexts cannot be compared for plaintext equality, and sorting random-looking bytes does not sort the underlying values. Decrypting every row inside PostgreSQL would expose the plaintext and turn an indexed lookup into a full-table scan. + +Searchable encryption solves this by separating **the value** from **the information needed to query it**. + +## How it works + +CipherStash stores a randomized ciphertext together with one or more encrypted index terms. Each term has one job: equality, ordering, text matching, or structured JSON search. + +For an email column that supports equality, the stored value is conceptually: + +```json +{ + "ciphertext": "randomized encryption of ava@example.com", + "equalityTerm": "keyed deterministic term" +} +``` + +The real [EQL payload](/reference/eql/core-concepts#anatomy-of-an-encrypted-value) also carries the metadata needed to validate and decrypt it. PostgreSQL never compares the randomized ciphertext. It extracts and compares the purpose-built term. + +```mermaid +flowchart LR + subgraph app["Your application"] + value["Plaintext value"] --> encrypt["Encrypt"] + needle["Plaintext query"] --> queryterm["Create query term"] + decrypt["Decrypt returned values"] + end + + subgraph db["Your PostgreSQL"] + payload["Ciphertext + index terms"] + index["Normal PostgreSQL index"] + match["Matching ciphertexts"] + payload --> index --> match + end + + encrypt --> payload + queryterm --> index + match --> decrypt +``` + +### Write path + +1. The Stack SDK or CipherStash Proxy receives a plaintext value in your application environment. +2. It encrypts the value with a unique data key and creates the terms declared for that column. +3. Your application sends the encrypted payload to PostgreSQL. +4. PostgreSQL stores the payload and indexes its terms. It never sees the plaintext or data key. + +### Query path + +1. The application turns the plaintext query operand into a term using the same cryptographic scope as the column. +2. It sends that term as a bound query parameter. +3. [EQL](/reference/eql) routes the comparison to the matching term, and PostgreSQL uses the appropriate functional index. +4. PostgreSQL returns only the matching ciphertexts. + +### Read path + +1. The application passes a returned payload to the SDK or Proxy. +2. ZeroKMS authorizes the key derivation and returns the material the client needs. +3. The client derives the data key locally, decrypts the value, and discards the key. + +Search and decryption are deliberately separate. PostgreSQL can find records, but it cannot decrypt them. + +## A concrete equality lookup + +The column's EQL type declares its capability. An equality-searchable email uses `public.eql_v3_text_eq`: + +```sql filename="schema.sql" +CREATE TABLE users ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + email public.eql_v3_text_eq +); + +CREATE INDEX users_email_eq + ON users USING hash (eql_v3.eq_term(email)); +``` + +The application encrypts both stored values and query operands: + +```typescript filename="lookup.ts" +const term = await encryptionClient.encryptQuery("ava@example.com", { + column: users.email, + table: users, +}) + +if (term.failure) { + throw new Error(term.failure.message) +} + +const rows = await db.query( + "SELECT id, email FROM users WHERE email = $1::jsonb::eql_v3.query_text_eq", + [term.data], +) +``` + +PostgreSQL uses `users_email_eq` to locate the matching term. The result still contains an encrypted email; the application calls `decrypt` only if it is authorized to reveal it. The [Quickstart](/get-started/quickstart) walks through the complete setup. + +## Query capabilities + +You select capabilities per column, so a lookup field does not need to carry the machinery for range or text search. + +| Capability | What it enables | Typical uses | EQL term | +|---|---|---|---| +| Storage only | Store and decrypt | Secrets or fields never filtered in SQL | None | +| Equality | `=`, `<>`, `IN`, grouping, uniqueness, equijoins | Email lookup, customer IDs, external references | Keyed HMAC (`hm`) | +| Ordering and range | Equality, `<`, `>`, `BETWEEN`, `ORDER BY`, `MIN`, `MAX` | Dates, amounts, scores, measurements | OPE (`op`) or block ORE (`ob`) | +| Free-text match | Probabilistic n-gram matching with `@@` | Names, descriptions, addresses | Encrypted Bloom filter (`bf`) | +| Searchable JSON | Path access, exact containment, ordered leaf comparisons | Flexible metadata and nested application records | Deterministic selectors and ordered leaf terms | + +EQL exposes these as concrete domain variants such as `text_eq`, `integer_ord`, and `text_match`. Your ORM integration maps its encrypted column types to the same model. See [EQL core concepts](/reference/eql/core-concepts#variants-declare-capability) for the full matrix. + +## It still uses PostgreSQL indexes + +Searchable encryption is practical because the encrypted terms fit PostgreSQL's existing index machinery: + +- hash or B-tree indexes serve exact matching; +- B-tree indexes serve ranges and ordering; +- GIN indexes serve encrypted text matching and JSON containment. + +PostgreSQL does not scan and decrypt the table. Once the query operand has been converted to a term, the query follows the same broad execution shape as an indexed plaintext query. + +In CipherStash's reference benchmarks at one million rows, exact matching and OPE range queries both run at about **0.12 ms**, within **1.2–1.4×** of equivalent plaintext PostgreSQL queries. Free-text matching is **100–400× faster** with its GIN index than with a sequential scan. Hardware and workloads vary; [Benchmarks](/reference/benchmarks) contains the environment, query plans, and reproducible suite. + +## Search and access control reinforce each other + +Database access and plaintext access are different permissions: + +| Operation | Where it happens | What is required | +|---|---|---| +| Find matching rows | PostgreSQL | Encrypted query term and the matching EQL operator | +| Read stored values | PostgreSQL | Database permission; the result is ciphertext | +| Reveal plaintext | Your application | Client key plus an authorized ZeroKMS derivation | + +With [provable access control](/solutions/provable-access), decryption can also require the signed-in user's identity. An application or agent can query a shared encrypted dataset, receive candidate rows, and decrypt only the values that identity is permitted to access. ZeroKMS records the derivation that made each decryption possible. + +Searchable encryption is what makes that model usable: applications can locate the right protected records before asking to reveal them. + +## Choosing capabilities + +Start from the behavior your product needs: + +- Use equality for identifiers and lookup fields. +- Use ordering for values that must support ranges or sorting. +- Use free-text match for human-entered text where token matching is useful. +- Use searchable JSON when flexible document structure is part of the data model. +- Use storage-only encryption when the application reads a field by some other key and never filters on it. + +The narrower type is often also the clearest schema. A `text_eq` column tells future maintainers that it is a lookup field; a `text_match` column says it supports token matching but not sorting. Unsupported operations fail rather than silently returning an incorrect result. + +## The security tradeoff + +The database needs some information to answer a query. Searchable terms expose defined relationships between encrypted values while keeping the values themselves secret. The capability you select determines that relationship: + +| Capability | What a database observer can infer | +|---|---| +| Storage only | Table shape, row count, nullness, update timing, and approximate value size—but no equality or ordering term | +| Equality | Which comparable values are equal, and therefore their frequency | +| Ordering | Equality, frequency, and the relative order of values | +| Free-text match | Probabilistic token overlap, repeated token sets, and approximate token-set size | +| Searchable JSON | Repeated paths and structures, equality of path/value pairs, document shape, and ordering of searchable leaves | + +The terms are keyed. Someone who steals an equality term cannot hash a plaintext dictionary and compare it without the key. However, frequency distributions, known records, and other auxiliary information can still make likely values easier to infer—particularly in small or predictable domains. + +Queries add their own observable patterns. A database operator can see repeated searches, which rows match, and where range boundaries fall. This is known as search-pattern and access-pattern leakage. The database still does not receive the plaintext query, but logs, endpoint behavior, or known test records can sometimes label an otherwise encrypted term. + +This is why capabilities are selected per column instead of enabled globally. If relative order is sensitive but exact lookup is acceptable, choose equality rather than ordering. If even equality groups are sensitive, use storage-only encryption and filter a smaller candidate set after decryption in the application. + +For the application and key-management trust boundary, see [Cryptography](/security/cryptography). For the exact payload fields and EQL operators behind each capability, see [EQL core concepts](/reference/eql/core-concepts). diff --git a/content/docs/get-started/index.mdx b/content/docs/get-started/index.mdx index 14f25bc..fd8bc9b 100644 --- a/content/docs/get-started/index.mdx +++ b/content/docs/get-started/index.mdx @@ -20,5 +20,5 @@ This section gets you from nothing to an encrypted, queryable field. It owns no - **Adopting CipherStash into an existing app?** [Integrations](/integrations) covers the platforms, ORMs, frameworks, and auth providers that wrap your existing client, so your queries keep the shape they already have. - **Can't change the application?** [CipherStash Proxy](/reference/proxy) sits in front of Postgres and encrypts on the wire. -- **Want to understand it before you install it?** [Concepts](/concepts) explains searchable encryption, key management, and identity-aware encryption. +- **Want to understand it before you install it?** [Searchable encryption](/concepts/searchable-encryption) explains how encrypted queries work and what each query capability enables. - **Evaluating for a security review?** [Architecture & security](/security) is written to be read end to end, starting with [Cryptography](/security/cryptography). diff --git a/content/docs/get-started/what-is-cipherstash.mdx b/content/docs/get-started/what-is-cipherstash.mdx index 49d4fe1..032df5d 100644 --- a/content/docs/get-started/what-is-cipherstash.mdx +++ b/content/docs/get-started/what-is-cipherstash.mdx @@ -87,7 +87,7 @@ So the goal is not to eliminate leakage. It is to choose capabilities whose leak **You are evaluating.** [Architecture & security](/security) is self-contained for a vendor review, starting with [cryptography](/security/cryptography). [Solutions](/solutions) covers the problems teams bring us. -**You are deciding whether this is the right tool.** [Concepts](/concepts) explains how it works and where it does not apply. The [comparisons](/concepts/compare) are written to be honest about what CipherStash is not. +**You are deciding whether this is the right tool.** Start with [searchable encryption](/concepts/searchable-encryption) to understand how encrypted queries work. The [comparisons](/concepts/compare) are written to be honest about what CipherStash is not. ## Performance diff --git a/content/docs/index.mdx b/content/docs/index.mdx index 8d82072..216b22a 100644 --- a/content/docs/index.mdx +++ b/content/docs/index.mdx @@ -23,7 +23,7 @@ agent, a curious insider: they all see ciphertext with no key. - + diff --git a/content/docs/integrations/supabase/auth.mdx b/content/docs/integrations/supabase/auth.mdx index f35e189..7aa9f86 100644 --- a/content/docs/integrations/supabase/auth.mdx +++ b/content/docs/integrations/supabase/auth.mdx @@ -127,7 +127,7 @@ The strategy's own factory and token errors follow the `@cipherstash/auth` `Resu ## Where to next - + The concept: federation, lock contexts, and what identity binding proves. diff --git a/content/docs/security/cryptography.mdx b/content/docs/security/cryptography.mdx index 19db2c0..fdfb922 100644 --- a/content/docs/security/cryptography.mdx +++ b/content/docs/security/cryptography.mdx @@ -15,7 +15,7 @@ This page is the single reference for CipherStash's cryptographic design. Every |---|---|---| | Data encryption | AES-256-GCM-SIV | Authenticated encryption with nonce-misuse resistance | | Equality search terms | HMAC-SHA-256 | Deterministic terms for exact-match lookups | -| Range and sorting terms | Block ORE | Order-revealing encryption ([Lewi-Wu 2016](https://eprint.iacr.org/2016/612)), with enhancements from [Bogatov et al. 2018](https://arxiv.org/abs/1810.05135) | +| Range and sorting terms | CLLW OPE or block ORE | EQL defaults to a directly sortable CLLW order-preserving term; block ORE is available as a pinned alternative ([Lewi-Wu 2016](https://eprint.iacr.org/2016/612), [Bogatov et al. 2018](https://arxiv.org/abs/1810.05135)) | | Free-text search terms | Encrypted Bloom filters | Trigram 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)) | | Payload integrity | BLAKE3 | Structure validation of the encrypted payload | | Payload encoding | MessagePack + Base85 | Compact binary serialization, stored as `jsonb` in PostgreSQL | @@ -130,7 +130,7 @@ An attacker must compromise **both** the ZeroKMS authority key and your client k 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, ORE, Bloom filter). +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](/reference/eql/core-concepts). 8. The data key is discarded. 9. The application stores the payload in PostgreSQL. @@ -144,12 +144,12 @@ An attacker must compromise **both** the ZeroKMS authority key and your client k ### Query path 1. The application encrypts a search term. -2. The SDK generates the appropriate index term (HMAC for equality, ORE for range and ordering, Bloom filter for free text). +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](/reference/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, ORE terms reveal relative order, and Bloom filter terms reveal probabilistic token membership. +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](/concepts/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. diff --git a/next.config.mjs b/next.config.mjs index c526466..55dc055 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -33,6 +33,13 @@ const config = { destination: "/get-started/quickstart", permanent: false, }, + // Concepts is a non-clickable sidebar group with no landing page. + // Keep its direct URL useful without rendering a synthetic Overview. + { + source: "/concepts", + destination: "/concepts/searchable-encryption", + permanent: false, + }, { source: "/integrations/prisma-next", destination: "/integrations/prisma", diff --git a/v2-redirects.mjs b/v2-redirects.mjs index 34258f8..831684e 100644 --- a/v2-redirects.mjs +++ b/v2-redirects.mjs @@ -48,7 +48,7 @@ export const v2Redirects = [ }, { source: "/stack/cipherstash/encryption/identity", - destination: "/concepts/identity-aware-encryption", + destination: "/solutions/provable-access", permanent: false, }, {