cryptmark — a serverless encrypted pastebin
How it works
-
A 16-byte salt and a 12-byte AES-GCM initialization vector are randomly
generated so that identical pastes never produce identical links.
-
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.
-
The paste is compressed (deflate-raw) and then encrypted with AES-GCM,
using the header (salt + iv + feature flag) as additional authenticated
data (AAD).
-
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
-
The body and title are encrypted. Without the password (or, for
keyless pastes, the link) they cannot be read.
-
The title is also shown in the browser tab and therefore recorded in
browser history and bookmark names. This is deliberate — it is what
lets a bookmark carry a meaningful name — so keep titles
non-sensitive.
-
Text is compressed before encryption, so the link length reveals
roughly how compressible (and how long) the paste is, not its content.
-
The link fragment is never sent to the server, but anything that syncs
your history or bookmarks (browser accounts, chat apps that preview
links) will see it. A keyless link is the key.
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
- 0x01: key is derived from a password, otherwise keyless.
-
0x02: payload is JSON with title metadata and body, otherwise plaintext
body.