How Secret.Broker Encrypts Your Data
The full pipeline, from your browser to the recipient's.
The short version
When you paste a secret into Secret.Broker, your browser encrypts it using XChaCha20-Poly1305 before anything leaves your machine. The encryption key is placed in the URL fragment (the part after the #). Your browser never sends the fragment to the server. The server stores ciphertext it can't decrypt. When the recipient opens the link, their browser pulls the ciphertext, reads the key from the fragment, decrypts locally, and the stored data is deleted.
That's the shape of the system. The rest of this article walks through each piece.
Step 1: Key generation
When you click "Create link," the first thing that happens is key generation. Your
browser generates a random 256-bit key using the Web Crypto API's
crypto.getRandomValues(). This key exists only in your browser's memory.
It's never transmitted to the server, never written to a cookie, never stored in
localStorage.
The key is 32 bytes of cryptographically secure randomness. At 256 bits, brute-forcing it would take longer than the age of the universe on any hardware that currently exists or is projected to exist.
Step 2: Encryption
With the key ready, your browser encrypts the secret using XChaCha20-Poly1305. This is an authenticated encryption cipher, meaning it provides both confidentiality (nobody can read the data) and integrity (nobody can tamper with it without detection).
Why XChaCha20-Poly1305 instead of AES-GCM?
- 24-byte nonces instead of 12. Larger nonces make random generation safe. No counter management, no nonce-reuse risk.
- Constant-time on all hardware. AES-GCM relies on hardware AES-NI instructions for safe performance. Without them, table-lookup implementations are vulnerable to timing attacks. XChaCha20 is constant-time in software.
- No practical performance difference in a browser. Both ciphers are fast enough that the bottleneck is network transfer, not encryption.
The encryption runs in your browser using libsodium, a well-audited cryptography library compiled to WebAssembly.
Step 3: Upload
After encryption, your browser sends the ciphertext to the server. Only the ciphertext. The key stays in the browser.
For small payloads (under 32 KB), the ciphertext goes to Azure Table Storage. For larger payloads, it goes to Cloudflare R2 via a pre-signed upload URL. Either way, the server receives an opaque blob of encrypted bytes. It can't distinguish a password from a poem.
The server responds with a secret ID. Your browser combines this with the encryption key to build the share link.
Step 4: The share link
The link your browser generates looks like this:
https://secret.broker/app/reveal/abc123#key=base64encodedkey
Everything before the # is the path. The server sees this part. It
identifies which encrypted blob to serve.
Everything after the # is the fragment. Browsers do not send
URL fragments to servers. This is defined in
RFC 3986, implemented by every browser, and is not a Secret.Broker feature. It's how the
web works. The key travels from sender to recipient without passing through any
server.
Step 5: Decryption and deletion
When the recipient opens the link, their browser extracts the key from the fragment, fetches the ciphertext from the server, and decrypts it locally using the same XChaCha20-Poly1305 cipher.
If the Poly1305 authentication tag doesn't verify, decryption fails. This means the data was corrupted or tampered with in transit. The recipient sees an error rather than corrupted output.
Once decrypted, the server deletes the stored ciphertext. The link is now dead. Opening it again returns nothing. If the secret was configured with a view limit greater than one, deletion happens after the last allowed view.
What the server knows
At no point does the server have access to:
- The plaintext secret
- The encryption key
- Any metadata about the content (length of the original text, whether it contains a password or a file)
The server knows that a blob of a certain size was stored at a certain time and was retrieved at a certain time. That's the minimum it needs to function. It can't know less and still serve the data.
File attachments
File attachments follow the same pipeline. The file bytes are encrypted alongside the text content into a single ciphertext blob. File names and MIME types are included in the encrypted payload, not sent as metadata. The server doesn't know whether you're sharing a password, a document, or both.
What this doesn't protect against
Encryption solves a specific problem: preventing unauthorized access to data at rest and in transit. It doesn't solve everything.
- If the recipient's device is compromised, the attacker can read the secret after decryption. This is true of any encrypted communication.
- If someone intercepts the share link (including the fragment), they can decrypt the secret. Send links through a channel you trust.
- If the sender's browser is compromised at the time of encryption, the plaintext is already exposed. Client-side encryption assumes the client is trusted.
These are the boundaries of the system. They're real, and it's worth understanding them. Within those boundaries, the server never has access to your data and never can.
Further reading
For the full protocol specification including Argon2id key derivation and domain binding, see the protocol documentation. For a deeper look at why the URL fragment matters, see URL fragment security. For a comparison of client-side and server-side encryption models, see client-side vs server-side encryption.