classified.

Security model

The v1 scheme, parameter by parameter

Format version 1, frozen since launch, as shipped in @classified/crypto. Describes notes; files use a parallel envelope built from the same primitives.

Maintained by the MAIN OÜ engineering team · Last reviewed 18 August 2026 · Verified against the shipped implementation

The parameters below are the ones the shipped client uses, so you can check the claims against the primitives rather than against our description of them.

What the server receives

A share is a single JSON document. The browser produces it before anything is uploaded, and it is the entire contents of what we store. A note blob looks like this:

{
  "v": 1,
  "alg": "A256GCM",
  "ciphertext": "<base64url>",
  "iv": "<base64url, 12 bytes>",
  "hasPassword": false,
  "wrappedKey": null,
  "kdf": null
}

There is no title field, no filename field, no sender field and no recipient field, because the service never asks for any of them. The iv is a nonce rather than a secret: it is required to decrypt and is useless without the key. When you set no password, wrappedKey and kdf stay null, and the key exists only in your link.

Parameters

ElementValueReasoning
Link secret128 bits from crypto.getRandomValuesShort enough to keep links manageable, far beyond brute-force reach. The extra characters a 256-bit secret would add to every link buy nothing against any realistic adversary.
Key derivationHKDF-SHA-256, empty salt, info string classified/v1 aes-gcm content keyExpands the 128-bit secret to a full AES-256 key. The salt is empty deliberately: the secret is already uniformly random, which is the condition under which RFC 5869 permits it. The info string separates this key from any future use of the same secret.
Content cipherAES-256-GCM, 96-bit nonce, fresh per shareAuthenticated encryption, so a tampered ciphertext fails to decrypt rather than returning plausible garbage. 96 bits is the nonce length NIST SP 800-38D recommends for GCM.
Password modePBKDF2-SHA-512, 310,000 iterations, random per-share saltWraps the link secret under a key derived from your password, so the link alone decrypts nothing. The iteration count follows OWASP's PBKDF2-SHA-512 guidance.
Share identifier72 bits of randomness, 12 base64url charactersIndependent of the key. Guessing an ID yields ciphertext the guesser cannot read, and consumes the share's single read in the attempt.

Where the key goes

For a share without a password, the 128-bit secret is base64url-encoded and placed after the # in the link. Fragments are resolved by the client and are not part of the request-URI sent to the server, per RFC 3986 section 3.5. That is a property of HTTP itself rather than a policy we apply, which is why the design leans on it: the key is not part of the request our servers receive, so it is not in the request logs. The guarantee is narrower than it first sounds, though. A fragment is fully readable by any JavaScript running on the page, analytics and third-party scripts included. It is withheld from the network layer, not from the page. That is why the limit on browser-delivered code, further down, is the one that actually bounds this design.

The key does travel wherever the link travels. Paste a link into a chat product and that product now holds the key. The fragment protects the key from our infrastructure, not from the channel you choose.

Password mode changes the trust model

With a password set, the browser generates the same 128-bit secret, encrypts that secret under a PBKDF2-derived key, and stores the result in wrappedKey alongside the salt and iteration count in kdf. Nothing usable goes in the link.

Use this when the link and the password can travel by different routes, such as link by email and password by phone.

Be precise about what it buys. The wrapped key, the salt and the iteration count are all stored in the envelope and returned to whoever fetches the share. Anyone holding that envelope can therefore try passwords offline, at whatever rate their hardware allows, with no rate limit available to us. The 310,000 PBKDF2-SHA-512 iterations make each attempt expensive, and that cost is the entire defence. A key derivation function multiplies an attacker's work; it cannot create entropy your password never had. Pick a password with real entropy rather than a memorable one.

What we can still see

A zero-knowledge design constrains content, not metadata. We can observe, and in some cases must observe:

None of that reveals content, and none of it is nothing. A claim of "we store nothing" would be false. The privacy policy covers the account-level records in more detail.

Where the design stops

One-time delivery is best-effort, not atomic

Reading a share is a fetch followed by a delete against an eventually-consistent key-value store. Those are two operations, not one transaction. Two requests arriving close enough together can both receive the payload before either delete lands. The stored TTL is the backstop that guarantees eventual removal, so the single read is a strong default rather than a cryptographic guarantee. Anything that must be read exactly once needs a protocol we do not claim to provide.

An automated first reader can consume the share

Link previewers, security scanners and mail gateways follow URLs. Any of them can be the first reader and burn the share before your recipient opens it. This is inherent to delete-on-read over HTTP rather than specific to us. When a recipient reports a dead link they never opened, this is almost always the cause.

Browser-delivered code is delivered by us

The encryption runs in JavaScript our servers send you. A provider able to serve modified client code to a targeted visitor could capture plaintext before encryption ever happens. Publishing source would help reviewers understand the intended design, but even that does not prove any particular visitor received that exact build. This limit applies to every browser-based end-to-end system. The mitigations are reproducible builds, signed native clients and independent audits. To be direct about where we stand: our source is not currently public and we have not commissioned an external audit, so this limit is one you have to take on trust today.

What none of this controls. A recipient who can read the content can screenshot it, photograph the screen or forward the plaintext. One-time sharing governs the copy on our infrastructure. It cannot govern a person.

Primary sources

  1. NIST SP 800-38D, Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC, for nonce length and authentication tag guidance.
  2. RFC 5869, HMAC-based Extract-and-Expand Key Derivation Function (HKDF), including when an empty salt is acceptable.
  3. RFC 3986, URI Generic Syntax, section 3.5, Fragment.
  4. W3C, Web Cryptography API, for the SubtleCrypto operations used throughout.
  5. OWASP, Password Storage Cheat Sheet, for PBKDF2 iteration guidance by hash function.