← Wszystkie wpisy
Polski

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. Nothing else derives 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.
  5. Prepend a 46-byte header: 4 magic bytes, the salt, the base nonce, and the length of the encrypted metadata.
  6. Upload the whole thing as a single HTTPS stream.

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

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

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 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.

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.

What the server sees, what it doesn’t

The server stores The server doesn’t see
The 10-character URL token (the /abc123 path component) The password
The encrypted blob The derived key
A derived token hash: SHA-256(HMAC-SHA256(key, "ttl-download-token" || 0x01)) — 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 The file’s contents
Timestamps, the blob’s size, the request user agent, and the TCP source port 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.

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 brute-force guess expensive (64 MiB of RAM per attempt wrecks GPU economics), but password123 is still guessable in reasonable time. If you let us generate the password, this isn’t your problem.

And none of this requires trusting us. A recipient who decrypts a file through a compromised ttl.space sees the same file they’d see through an honest one. In both cases the server’s only job is passing ciphertext. That’s the point.

Opublikowano · Zaktualizowano .