Encryption: the protection model, plainly
What happens to an uploaded bundle should not be a mystery. This page describes the real encryption of the OKF Knowledge Hub – what is protected, how, and explicitly what is not. Honesty here matters more than a strong word on a marketing sheet.
A note on the name. This page was drafted as "Client-Side AES-GCM." That was wrong. The encryption runs server-side, per user. The text below describes what the code actually does.
The model in one paragraph
Every user gets their own key, derived from a secret master key and the stable Google account ID. Their bundles are encrypted with AES-256-GCM before they reach storage. Object storage (S3) holds ciphertext only. Plaintext exists transiently, inside an authenticated request from the owner – exactly when the Hub renders their library, builds the graph, or produces a RAG answer. There is no admin path that reads another user's plaintext.
Key derivation
There is no single key for everyone. From the server-side OKF_MASTER_KEY and the owner's stable Google sub, the Hub derives a 256-bit per-user key via HKDF-SHA256 (a key-encryption key). Two users therefore have different keys even under the same master. On every access the API also checks whether the signed-in person owns the requested bundle.
The encryption itself
Encryption uses AES-256-GCM, an authenticated scheme: it protects confidentiality and integrity at once. Each operation draws a fresh 12-byte initialization vector. The stored object is a single buffer in fixed order:
[ 12-Byte IV ][ 16-Byte GCM-Auth-Tag ][ Chiffretext / ciphertext ]
The auth tag lets decryption detect tampering with the ciphertext – a modified file does not decrypt, it fails. Not only the bundles themselves are protected this way, but also each bundle's semantic vector index, stored encrypted alongside it.
What this protects
This architecture protects stored files from straightforward plaintext access in the storage backend: anyone seeing only the S3 bucket sees ciphertext. And it cleanly separates bundles by account – one user's key does not decrypt another's data. Together with the ownership check at the endpoints, that draws a clear boundary between different users' knowledge bases.
What it is not
First things first: this is not zero-knowledge and not end-to-end encryption. The server application holds the necessary key material and decrypts content for authorized functions – otherwise it could neither render the library nor run RAG. Anyone needing a trust model where the operator technically cannot read the content will not find it here. Such a model would keep the key with the user and let the server see only ciphertext.
Hence the practical rule, also stated in the guide: do not upload passwords, private keys, access tokens, or data that may not be stored on a third-party server. And do not use the AI assistant with content that may not be transmitted to OpenRouter and the chosen model provider – because a chat request deliberately leaves the server-side boundary.
Semantic search stays local
A point often missed: the embeddings for semantic search are computed by a local model on the server. Building the vector index sends no content off the machine. The index is stored encrypted. Only the AI chat transmits the question and selected excerpts outward – everything before it stays within the server-side boundary.
On the roadmap
Two things are deliberately still open and listed in the changelog. First, key rotation: currently the per-user key encrypts bundles directly, without an intermediate data-encryption-key envelope. Such an envelope would be needed for regular key rotation – it arrives when rotation is actually needed. Second, an optional client-side encryption model for users who do not want to trust the server; that would be a different product and is not implemented today.
Continue
How sign-in, storage, and AI processing interact in practice is in the privacy and security section of the guide. Known limits and error cases are covered in troubleshooting.
Beschreibung nach der realen lib/crypto.js und server.js dieses Projekts: HKDF-SHA256-Per-User-KEK, AES-256-GCM, Layout [IV][Tag][Chiffretext], Chiffretext-only auf S3. Stand 2. August 2026. / Description per this project's real lib/crypto.js and server.js.