Private by default
Encryption and decryption happen client-side with Web Crypto before any secret is sent over the network.
A plain-language overview first, with technical detail available when you want it. Secure Share encrypts in your browser and shares only what is needed to deliver a one-time reveal.
The short version: the server stores encrypted blobs, not your plaintext and not your decryption key. The link works once, then the record is deleted.
Encryption and decryption happen client-side with Web Crypto before any secret is sent over the network.
Successful retrieval burns the record. Expired secrets are blocked at expiry and purged by scheduled cleanup.
A password adds a second factor. The server verifies proof material, not your raw password.
Failed reveal attempts trigger temporary lockouts and eventual burn to limit guessing windows.
Read the cards above and the bold headings in each journey step below. You will understand what happens and what stays private.
Read the full journey rows and open the deep-dive sections for algorithm choices, verifyHash, peppering, and lockout controls.
Each step starts with plain language. Open technical details only when you want the lower-level cryptography and verification flow.
Generates random baseKey (32 bytes) + salt (16 bytes), derives derivedKey = PBKDF2(passwordOrSynthetic, salt, 1,000,042), builds finalKey = SHA-256(baseKey || derivedKey), encrypts with AES-256-GCM, and computes verifyHash.
#...).Fragment carries packed v2 data: id + baseKey + salt + flags. Server-side create record contains id + ciphertext + verifyHash + expiresIn. Fragment data is client-handled.
Client submits verifyHash = SHA-256(derivedKey). Stored value is pepper-protected: v1.HMAC-SHA256(verifyHash, VERIFY_HASH_PEPPER). Verification uses constant-time comparison before one-time release.
Successful retrieval burns the record. Expired secrets are blocked at expiry check and purged by scheduled cleanup. Failed attempts trigger lockout windows and eventual burn to limit brute-force budgets.
Encryption uses AES-256-GCM for confidentiality and integrity. Password-based derivation uses PBKDF2-SHA256 with 1,000,042 iterations, plus a random 16-byte salt.
When a password is used, Secure Share combines random key material with the password-derived key using finalKey = SHA-256(baseKey || derivedKey). Decryption requires URL fragment data and the correct password path.
The link stores packed binary v2 data in #fragment: version + UUID + key + salt + flags. It is fixed length (66 bytes before Base64URL encoding).
The fragment is processed in-browser and is not included in HTTP request paths, helping keep key material out of server logs.
The client computes verifyHash = SHA-256(derivedKey), where derivedKey comes from PBKDF2 using either the user password or a synthetic password for non-password shares.
Before saving, the server protects that value with a secret environment pepper: stored = v1.HMAC-SHA256(verifyHash, VERIFY_HASH_PEPPER). During retrieval, submitted verifyHash is HMAC-wrapped and compared in constant time. Failed attempts trigger lockouts and eventual burn.
The service cannot protect against endpoint compromise (malware, clipboard leaks, captured screenshots), weak passwords, or link forwarding to unintended recipients.
Use a strong password, send link and password over different channels when possible, and choose shorter expiry windows for sensitive secrets.
API keys, recovery codes, credentials, and one-time notes. Keep messages under 512 characters.
No plaintext and no decryption key are stored server-side. The system stores encrypted payloads and retrieval metadata only.
Without a password they may unlock non-password links. For sensitive content, use password protection and share password separately.
Create requests use Turnstile checks; retrieval attempts are lockout-protected and secrets are burned after repeated failures.
Secure Share was built with coding AI assistants as development tools, not as a substitute for engineering judgement. Product direction, architecture, security boundaries, and final implementation decisions were reviewed and directed by Ben Cooper.
The goal was deliberate execution over speed: clear trust boundaries, auditable behaviour, and plain-language transparency around what the service can and cannot see.