cryptmark — a serverless encrypted pastebin

How it works

  1. A 16-byte salt and a 12-byte AES-GCM initialization vector are randomly generated so that identical pastes never produce identical links.
  2. A key is derived from the provided password via PBKDF2 (SHA-256). With a password the derivation runs 600,000 iterations; with no password the key is derived from an empty password and the random salt over a single iteration, so no secret is required to decrypt — the link itself is the key.
  3. The paste is compressed (deflate-raw) and then encrypted with AES-GCM, using the header (salt + iv + feature flag) as additional authenticated data (AAD).
  4. The salt, iv, feature flag byte, and ciphertext are concatenated into a single payload, which is then base64url-encoded as a whole and appended to the page URL as a fragment (after "#"). Because the fragment is never sent to a server, the encrypted paste stays entirely in the browser.

Opening a cryptmark link runs these steps in reverse.

What is and isn't hidden

Development

The payload format lives in crypto.js and is covered by tests/ (npm test, needs Node 18+). Legacy fixtures there pin the wire format so old links keep working. The CSP in _headers allows the inline script and style by hash; after editing either in index.html, run npm run csp to regenerate it (the test suite fails when it is stale).

Link structure

The base64url fragment decodes to the following byte layout:

| salt 16B | iv 12B | flag | encrypted text + 16-byte tag |
+----------+--------+------+------------------------------+

Feature flag bitfield