← Toate articolele
Română

How ttl.space's end-to-end encryption works

Upload a file to most services and the server sees everything. Dropbox, Slack, email: the bytes pass through a machine that can read them. Admins can read them. Insiders with bad motives can read them. A stolen database dump reads them just fine.

End-to-end encryption breaks that symmetry. The file is encrypted on your machine before it leaves, and it stays encrypted until it’s back on a device you control. Our server only stores bytes it can’t read.

A subpoena turns up ciphertext. A database leak turns up ciphertext. That’s the promise, and the whole implementation exists to keep it.

What happens when you upload

In your browser, before a single byte hits the network:

  1. Generate a salt (16 bytes) and a base nonce (24 bytes), both cryptographically random.
  2. Run Argon2id on your password and the salt: 3 passes, 64 MiB of RAM, 256-bit output. That’s the encryption key. The password itself is used nowhere else; the download token described below is derived from this key with HMAC, not from the password.
  3. Split the file into 64 KiB chunks. Encrypt each one with XChaCha20-Poly1305. The nonce for chunk i is the base nonce XOR i+1. Each chunk carries a 16-byte authentication tag: no silent corruption, no undetected tampering.
  4. Encrypt the filename, file size, and chunk size as a “chunk 0” metadata block using the same cipher and the base nonce unchanged (counter 0). That is why the file chunks start at i+1.
  5. Prepend a 46-byte header: 4 magic bytes, the salt, the base nonce, and the length of the encrypted metadata.
  6. Send it over HTTPS: a small file in one request, a larger one in 16 MiB parts. The browser fingerprints each part with SHA-256, and the server stores a part only if what arrived matches.

The on-wire layout is small enough to fit on one line:

MAGIC(4) || salt(16) || baseNonce(24) || metaLen(2) || encMeta || encChunks...

The server writes the encrypted blob under a 10-character random token and returns https://ttl.space/Kx7mQp2wNr. The password is shown separately on the success page. You share both.

Why XChaCha20-Poly1305

The 24-byte nonce isn’t cosmetic. Plain ChaCha20-Poly1305 uses 12 bytes: that’s only 2⁹⁶ possible values. Draw randomly from a 12-byte space enough times and you eventually collide, and under the same key, nonce reuse breaks the cipher. Every byte of plaintext from both colliding messages leaks. At 24 bytes we have 2¹⁹² values: we can pull a fresh nonce from crypto.getRandomValues() for every upload, forever, and the probability of a collision stays astronomically small. It’s the difference between “careful with your counter” and “just generate one.”

Why Argon2id with 64 MiB

Password-derived keys are only as strong as the cost of guessing them. Argon2id with 64 MiB of memory per attempt makes GPU-based brute force economically painful: a rig that can test a billion SHA-256 hashes a second manages only a fraction of a percent of that against Argon2id at these parameters. If you let us generate the password, the space is already large enough to be untouchable. If you set your own, Argon2id buys you room.

File requests: a password nobody types

A file request uses the same format with one change. When the requester creates the request, their browser makes an X25519 key pair and gives the server only the public half. A sender’s browser encrypts the file as above with a random password, then seals that password to the public key with libsodium’s crypto_box_seal. The server stores the sealed password next to the ciphertext and can open neither. The requester’s browser opens the seal with the secret half, which never leaves it, and decrypts the file like any other.

What the server sees, what it doesn’t

The server stores The server doesn’t see
The 10-character URL token (the /Kx7mQp2wNr path component) The password
The encrypted blob The derived key
A derived token hash: SHA-256(HMAC-SHA256(key, "ttl-download-token" || 0x01)), which authenticates the 32-byte download token sent by the client The filename
Your IP, twice: as a keyed hash (audit index) and as an AES-GCM ciphertext the operator can decrypt for abuse investigation; beside it, the country, city and network it belonged to at that moment The file’s contents
Timestamps, the blob’s size, whether it is a file or a text note, the request user agent and the browser’s language list Anything that can become plaintext

Nothing the server holds can be run backwards into the plaintext file. The stored token hash authenticates “this download belongs to that upload” without the server ever touching the key. A subpoena, a database leak, or an insider dump turn up ciphertext and the audit metadata above, but no filename, no file contents, no password, and no derived key.

There is one way a password can reach us: someone who has it reports the file for abuse and adds the password to the report. We then keep that password encrypted for 30 days, only to hand to authorities on a valid legal request. We never open files ourselves.

Who this is for. If you’re sending medical records, legal documents, unreleased code, or anything you’d regret leaking, end-to-end encryption is the right default. If the file is already public, it doesn’t matter. The cost of doing it right is on the order of a second of Argon2id on a modern laptop (longer on older or mobile hardware).

Where this falls short

E2E protects the pipe between two devices. It doesn’t follow the file after it lands. If the recipient’s device has a keylogger, a malicious browser extension, or somebody reading over their shoulder, they see what the recipient sees. Nothing in this protocol changes that.

Passwords are the other weak link. Argon2id makes each guess expensive, but password123 is still guessable in reasonable time; a generated password is not.

How much you have to trust us depends on the client. The CLI is open source and never loads code from our server, so a compromised ttl.space could only hand it ciphertext. In the browser the decryption code itself arrives from our server on every visit, so a compromised server could serve modified JavaScript that captures your password. If that matters for the file you are sending, use the CLI.

Publicat · Actualizat .