How it works
A plain-language walkthrough of what happens when one organization sends a document to another through fxe. No prior cryptography knowledge required.
The one-line summary
The sender's computer wraps the file in a cryptographic envelope addressed to the recipient's keys. The relay carries the envelope without being able to open it. The recipient unwraps it, signs a receipt, and the relay then throws the ciphertext away.
The message flow
What's in the envelope
Before the file ever leaves the sender's computer, it goes through three layers:
- A fresh 32-byte content-encryption key (CEK) is generated for this one message. The file is encrypted with the CEK using ChaCha20-Poly1305 — a modern authenticated cipher. The authenticated-data field binds the ciphertext to a specific message ID, sender, recipient, and metadata hash, so a copy-paste attack across messages fails verification.
- The CEK is wrapped twice, once with ML-KEM-768 (a NIST post-quantum KEM) and once with X25519 (a classical curve). The two shared secrets are combined through HKDF-SHA256 along with the recipient's public keys, so an attacker would need to break both ML-KEM-768 and X25519 to recover anything.
- The envelope metadata is signed with the sender's per-bundle Ed25519 key over a canonical JSON form (sorted keys, no whitespace) and domain-tagged so signatures can't be replayed in a different protocol position. The recipient verifies before decrypting.
File-size cap: 100 MiB of ciphertext per message. The relay rejects larger uploads outright. Documents are expected to be PDFs; the relay doesn't inspect bodies, but recipients assume application/pdf and integrations on the other side will treat it as such.
What the relay sees
Server sees
- Org names, fax-number → org mapping
- Public identity and key-bundle public keys
- Opaque ciphertext blobs (size and timing only)
- Wrapped per-message CEKs (still encrypted)
- Sender / recipient signatures (verifying only)
- Audit log: who sent to whom, when, message IDs
- An API-key hash for verification (not the key itself)
Server never has
- Plaintext file contents
- The content-encryption key in unwrapped form
- Any private key — identity, request signing, ML-KEM, X25519, per-bundle Ed25519
- Anything encrypted under a recipient's bundle
What an attacker who reads the database learns — and doesn't
Suppose someone exfiltrated the entire relay database and ciphertext store. They would learn:
- Which orgs are registered, and which fax numbers each owns.
- Send/receive metadata: who sent to whom, when, ciphertext sizes, message IDs.
- All public keys.
They would not learn the plaintext of any message. Recovering plaintext requires unwrapping a per-message CEK, and each CEK is protected by the hybrid ML-KEM-768 + X25519 wrap — the attacker would need to break both primitives.
If an attacker had write access to the database, they could forge audit-log entries, but they could not deliver a decryptable forged message to any org. Forged envelopes are rejected because the recipient verifies the sender's per-bundle signature and because the AEAD authenticated-data field binds the ciphertext to a specific sender, recipient, and metadata hash.
What's not protected
Being honest about the limits matters. fxe does not protect against:
- A compromised member-org client: if an attacker has code execution on your sending or receiving workstation, they can read plaintext — that's true of any E2EE system. Protect your keys at rest, restrict access.
- Traffic-analysis / metadata privacy: ciphertext size, timing, and the social graph of who-talks-to-whom are visible to the relay. fxe does not provide cover traffic or mixnet-style obfuscation.
- Side channels on the relay or on members' machines (timing, power, etc.).
Where keys live
Each organization holds three kinds of keys, generated on its own hardware during invite redemption and never uploaded:
- Identity key (Ed25519): the org's long-term root. Signs key bundles and rotations.
- Signing key (Ed25519): authenticates API requests to the relay (replaces "API password" — the request itself is signed, not bearer-token).
- Encryption bundle (ML-KEM-768 + X25519 + Ed25519 signer): used to receive messages. The public half is published to the relay; the private half stays with the org.
After delivery
When the recipient picks up a message, they sign a receipt — over a canonical JSON with the message ID, ciphertext hash, and timestamp — using their per-bundle Ed25519 key. The relay verifies the signature and deletes the ciphertext immediately. Messages that aren't picked up sweep on a TTL (default: one week).
Rotating keys
Each org has two distinct kinds of key material with very different rotation policies:
- The identity key never rotates. It's the root of trust — its public half is registered at invite redemption and used to sign new key bundles. Losing the identity private key means the org must be re-invited.
- Key bundles rotate on demand. A new bundle (a fresh ML-KEM-768 + X25519 + per-bundle Ed25519 set) is signed by the identity key and published; the old bundle is retired. Old bundles are kept indefinitely so messages still in flight that wrapped a CEK to them can still be decrypted.
During the brief overlap when more than one bundle is active, senders wrap each message's CEK to all active recipient bundles, so the recipient can decrypt with whichever bundle it currently holds.