Zero-Knowledge Encryption
How it works
Konfidant encrypts every share on the sender’s device, before anything is sent to the API. The API stores and delivers ciphertext only.
- The client generates a fresh random 256-bit key for the share and encrypts the content with AES-256-GCM in the KNF1 format. For files, the file name and MIME type are encrypted together with the content.
- The client sends only the ciphertext to the API (
POST /api/v1/texts, or the three-step file flow). The API returns adownload_urlthat carries a server-issued, single-use token in the URL fragment:#t=<token>. - The client appends the key to the fragment and sends the resulting share link to the recipient:
- The recipient opens the link. The page reads the fragment, sends only the token to Konfidant, receives the ciphertext once and decrypts it locally. The ciphertext is deleted after it has been delivered.
The URL fragment (everything after #) is never sent to a server by browsers, so the key never reaches Konfidant.
Organizations with a custom download domain receive links on that domain instead of download.konfidant.app.
The download_url returned by the API is not a working share link on its own. Without &k=<key> the recipient
cannot decrypt the content. Conversely, the full share link is the secret: anyone holding it can open the share once.
Do not log it.
What Konfidant can and cannot see
Consequences:
- Lost links cannot be recovered, not even by Konfidant support: there is no copy of the key.
- The single-use token is issued by Konfidant and is not a secret from Konfidant. It only controls access to the ciphertext; the key is what protects the content.
- Web clients trust the JavaScript served by Konfidant. When you share or open a link in the browser, the
encryption code is delivered by Konfidant, so a compromised or malicious deployment could in principle serve code
that leaks keys. Download pages load no third-party scripts and send a strict Content Security Policy to reduce
this risk. If this matters for your threat model, create shares with an official SDK, whose code you install and
can review, and open them with the SDK’s
openSharemethod.
Use an official SDK
The official SDKs implement KNF1, the upload flow and link handling, and are tested against the official KNF1 test vectors. We strongly recommend them over raw HTTP calls.
Calling the API without an SDK
The API does not accept plaintext. A raw HTTP client must implement KNF1 itself:
- Generate a 32-byte key with a cryptographically secure random generator. Never reuse a key.
- Encrypt the content as KNF1 (below).
- Create the share:
- Text:
POST /api/v1/textswith{"ciphertext": "<standard base64 of the KNF1 bytes>", "ttl_hours": 24}. - File: follow Sharing an Encrypted File.
- Text:
- Build the share link:
download_url + "&k=" + base64url(key), where the key is unpadded base64url (43 characters). Send the key only in the fragment, never in a request body, query string or header.
ttl_hours is optional; when omitted, the share uses the maximum lifetime allowed by your plan.
Opening a link
To open a share link programmatically, take the origin of the link and send the token from the fragment:
The response is the KNF1 ciphertext (application/octet-stream), or 410 if the share was already opened or has
expired. Opening a share consumes it. Decrypt the response locally with the key from &k=. No API key is needed.
The KNF1 format
KNF1 is a chunked AES-256-GCM format (the STREAM construction): every chunk has its own nonce, which prevents reordering, duplication and truncation of chunks.
Header
The header is plaintext and is authenticated as additional data (AAD) of every chunk.
Plaintext stream
kind:01for text,02for file.name: UTF-8 file name, at most 1 024 bytes. Empty for text.mime: UTF-8 MIME type, at most 255 bytes. Empty for text; may be empty for files.content: the raw file bytes, or the UTF-8 text.
Chunks
Split stream into chunks of chunk_size bytes; the last chunk has 1 to chunk_size bytes. With N chunks,
for chunk index i (0-based):
Size
For files, size is the ciphertext_size you declare in POST /api/v1/files. The plan’s file size limit is
applied to the content; the server allows for the KNF1 overhead.
Decryption
- Check
magic,reserved == 0and thatchunk_sizeis in range. Reject anything else. - Read sealed chunks of
chunk_size + 16bytes; the final sealed chunk is the remainder (17 tochunk_size + 16bytes) and is opened withlast_flag = 01. - Treat any authentication failure as fatal and discard all output: it means a wrong key, or modified, reordered or truncated data.
- Parse
metafrom the start of the decrypted stream and reject unknownkindvalues or inconsistent lengths. The remaining bytes are the content.
Test your implementation against the official KNF1 test vectors, which the official SDKs reproduce byte for byte. If you are not sure, use an SDK.
