How it works
This page is for checking the work rather than taking it on faith. It describes what happens to a file, what the server ends up holding, and where the design stops helping you.
Two kinds of encryption, doing two different jobs
A file is encrypted twice over, with two different systems, because neither one can do the whole job alone.
- The file: symmetric
- The body is encrypted with ChaCha20-Poly1305 using a single random 256-bit key. Symmetric encryption is fast and works on a file of any size, but it needs both sides to hold the same key, and you have no way to get that key to somebody safely.
- The key: asymmetric
- So the key is itself encrypted, to the recipient's public key, using HPKE with X25519. Public key encryption solves the delivery problem, because the public half can be handed to anyone, but it is slow and only practical on something small. A 256-bit key is small.
- Together
- The fast one carries the file, the slow one carries the key to it. This is the standard arrangement, usually called hybrid encryption, and it is what lets a 5 GB file be sealed to somebody you have never exchanged a secret with.
Why ChaCha20-Poly1305
It is an authenticated cipher: every chunk is encrypted and carries a tag proving it has not been altered, so a tampered chunk fails to decrypt instead of decrypting to garbage.
The main alternative is AES-GCM, which is equally strong but is only fast on processors with dedicated AES instructions, and software implementations of AES have a history of leaking key material through timing side channels. ChaCha20 runs at full speed in plain software on any processor and is constant time by construction.
It is the same pairing used by TLS 1.3 and WireGuard.
The path a file takes
-
01
A key is made on the sender's Mac
Every file gets its own fresh random 256-bit key. It is generated locally and it is never sent anywhere in the clear.
-
02
The body is sealed, chunk by chunk
The file is encrypted with ChaCha20-Poly1305 in 1 MiB chunks. Each chunk carries its own nonce with a counter in it, and the final chunk is marked as final.
-
03
The key is sealed to the recipient
The per-file key travels in a header sealed with HPKE, using X25519, SHA-256 and ChaCha20-Poly1305, to the recipient's public key. Only the recipient's Mac holds the private half, so only that Mac can open it.
The seal is HPKE's authenticated mode, keyed on the sender's own private key and on the transfer id. Opening it therefore requires naming which sender it came from, and the recipient names the one the safety words were derived from. A header made by anyone else, or one lifted from a different transfer, does not open.
-
04
It is uploaded
The server receives the recipient's public key, the sealed header, the encrypted bytes, and a SHA-256 of those bytes.
-
05
The recipient checks, then opens
The receiving Mac verifies the SHA-256 of the ciphertext before it decrypts anything. Then it opens the header, recovers the file key, and decrypts the chunks in order.
Registration, once per Mac: a key pair is generated, the private half stays in the Keychain, the public half goes to the server.
What the server holds
- It has
- The recipient's public key, an opaque sealed header, the ciphertext, a SHA-256 digest of that ciphertext, and its size in bytes.
- It does not have
- The per-file key, or the file's contents in readable form. The file's name is inside the sealed header, so the server holds it but cannot read it.
- Why that holds
- The file key only exists sealed to a public key whose private half stays in the recipient's Keychain. The server cannot decrypt because it does not have the material to, not because it has agreed not to. That is a property of the construction rather than a promise about behaviour.
Why the final chunk is marked
Chunked encryption has an obvious failure mode: drop the last frame and the file still decrypts, just shorter. Marking the final chunk closes that. A truncated or reordered stream fails to decrypt rather than quietly handing you an incomplete file that looks fine. The per-chunk counter does the same job for order.
The safety words
These are not the three words you hand out. Your address is three words and it is meant to be shared. The safety words are a separate set of four, shown to both sides once a transfer exists, and they are only for checking against each other.
The server never sends them. Each Mac computes the four words locally, by hashing the two public keys of the transfer together with the transfer id, and mapping the digest onto the word list.
SHA-256(sender public key ‖ recipient public key ‖ transfer id)
That is what catches a machine in the middle that has substituted one of the public keys for its own. The two Macs would then be hashing different inputs, and the four words they each derive would not match.
Four words is not enough for that to hold against a server prepared to spend compute on it. A machine in the middle substitutes a key in both directions and picks both of them, so rather than guessing a target it can search for a pair whose words happen to agree. Four words from a 1295-word list is about 41 bits, and searching for an agreeing pair is a birthday problem: around two million candidate keys, which is minutes of ordinary processor time. Lengthening the words is the fix and it has not been done yet.
The check is only as good as the comparison. It catches nothing unless the two people read the words to each other over a channel the server does not carry, and stop the transfer when the words differ.
Identity, codes and expiry
- Identity
- A key pair in your Mac's Keychain. No account, no email, no password, no sign-up.
- Your address
- Three rotating words that expire, ten minutes on the free tier and an hour on a paid balance. Ask for new ones and the old ones stop working immediately. Permanent and custom codes are the exception: they never expire, and rotating your words does not retire them.
- Transfers
- Expire after 24 hours, at which point the stored object is deleted.
What this does not protect against
None of these are weaknesses in the encryption. They are the things encryption does not reach, and no amount of it would.
- Metadata. The server sees sizes and timing, and it sees which device sent to which device. Encrypting the contents does not hide the shape of the traffic.
- A compromised Mac. Everything here assumes the two endpoints are sound. Anything with a foothold on the sending or receiving machine reads the file before it is encrypted or after it is decrypted, and none of the above helps.
- Safety words nobody compares. The four safety words only catch a machine in the middle if both people actually read them to each other. Nothing makes them do it, and the transfer completes either way.
- Whoever has your three words. Anyone holding your current words can send you a file. That is what they are for.
Check it yourself
The client and the server are both open source, and the server is yours to run. Point the app at your own and nothing above changes.