Skip to main content

Documentation: Secure Messages

Security and encryption

This article explains, in technical terms, how Secure Messages protects a message and what the server can and cannot see. It is written for the sceptical reader - the security model only works if you can verify it.

Zero-knowledge encryption

When a message is created, the sender's browser:

  1. generates a random 128-bit key using the browser's cryptographically secure random generator (crypto.getRandomValues);
  2. derives a 256-bit AES key from it with PBKDF2-SHA256 (100,000 iterations, random 16-byte salt);
  3. encrypts the message with AES-256-GCM (random 12-byte IV) via the browser's native Web Crypto API - GCM is authenticated encryption, so any tampering with the stored data makes decryption fail outright;
  4. sends only the packed result (salt + IV + ciphertext, base64-encoded) to the server.

The random key never leaves the browser as part of that request. It becomes the fragment of the secure link - the part after the #. URL fragments are handled entirely client-side: when the recipient opens the link, the browser requests the page without the fragment, receives the ciphertext, and decrypts it locally. The server, its logs and its database never contain the key.

Password protection is part of the key

When a message is password-protected, the password is concatenated into the secret that PBKDF2 derives the AES key from. There is no server-side password check at all - a wrong password simply produces a key that fails GCM authentication. This means the password cannot be brute-forced against the server, and nobody can "reset" it.

What the server does store

  • The ciphertext (useless without the link).
  • Metadata: an unguessable 128-bit message identifier, the optional reference, the expiry date, the burn flag, the chosen recipient address for frontend messages, and who created it (user id, or "guest").
  • A bcrypt hash of the password, stored only so the backend can indicate that a message is password-protected. It is never used to verify anything.

The stored payload is validated to contain only base64 characters, which structurally rules out stored-XSS through the message body; the decrypted text is additionally rendered as plain text, never as HTML.

The e-mail delivery trade-off

One deliberate, documented weakening: when a link is delivered by e-mail (the public form does this automatically; the backend offers it as an option), the full link - including the key in the fragment - passes through your server's mail system and the e-mail infrastructure between you and the recipient. For that moment, confidentiality rests on your SMTP transport (use TLS) and on the recipient's mailbox security, exactly as with any e-mailed secret. Burn-after-reading and expiry limit how long an intercepted link would remain usable. If you copy the link yourself and share it through another channel, the key never touches the server after creation.

Burn-after-reading and revocation

A burn message is deleted server-side the moment the recipient reveals it; afterwards the ciphertext no longer exists anywhere. Deletion is authorised by possession of the unguessable message identifier (the same value needed to reach the page at all), and the endpoint answers identically whether or not a message existed, so it cannot be used to probe for valid identifiers. Backend users can revoke any message at any time by deleting it from the list.

Other measures

  • Message pages send noindex, nofollow, so links never end up in search engines or their caches.
  • The public send endpoints are CSRF-protected and validate the chosen recipient against the configured whitelist server-side; the mail endpoint additionally refuses any link that does not point at this site's own message page, so it cannot be abused to relay foreign URLs.
  • All activity is auditable in the User Actions Log without ever exposing content or usable links.

Honest limitations

Client-side encryption trusts the client: a compromised browser, a malicious browser extension, or a tampered-with server that ships modified JavaScript could undermine it - this is inherent to every in-browser encryption tool. Keep your site updated, serve it over HTTPS, and treat the e-mail channel as the weakest link. For secrets that must survive a hostile mailbox, use password protection with the password shared out-of-band.