Principles
This page describes what is implemented in Ownfold’s encryption core and server today. Planned features, including Private Vault, aren’t covered until they exist.
- The server never holds a key that decrypts your data. Not your password, not a password hash it could test guesses against, not your recovery key. Every key it stores is wrapped by another key it doesn’t have.
- No home-made cryptography. Ownfold composes established implementations from the RustCrypto and dalek projects. It implements no primitive itself.
- Everything is bound to its place. Every encryption and signature commits to a purpose label and the identifiers of the object it protects, so ciphertext can’t be swapped between files, moved between folders or replayed under a different meaning.
- One core, every platform. The same Rust crate compiles natively and to WebAssembly, so every client reads and writes byte-identical formats, checked against shared test vectors.
Primitives
| Purpose | Construction |
|---|---|
| Symmetric encryption | XChaCha20-Poly1305, random 192-bit nonces |
| Key derivation | HKDF-SHA256 with domain-separated labels |
| Password sign-in | OPAQUE (RFC 9807): ristretto255, SHA-512, Argon2id |
| Password stretching | Argon2id, 64 MiB, 3 passes, 1 lane |
| Public-key encryption | HPKE (RFC 9180) base mode, X-Wing KEM (X25519 + ML-KEM-768), HKDF-SHA256, ChaCha20-Poly1305 |
| Signatures | Ed25519, strict verification |
Every symmetric ciphertext — wrapped keys, names, metadata, content blocks, thumbnails — uses a single envelope format:
envelope = version (0x01) || algorithm (0x01 = XChaCha20-Poly1305)
|| nonce (24 bytes) || ciphertext || tag (16 bytes)
aad = version || algorithm || "ownfold/v1" || lp(purpose) || lp(ids…)
The header is authenticated, so a ciphertext can’t be relabelled as another version or algorithm. Associated data is length-prefixed, so two different contexts can never encode to the same bytes.
Your keys
Everything you own hangs from one random account master key. It is stored on the server twice, wrapped two independent ways — once by a key derived from your password, once by your recovery key — and the server can open neither.
Red: known only to you. Every other key is stored by the server, wrapped by a key it doesn’t have.
Changing your password re-wraps the master key and nothing else. No file is re-encrypted.
Signing in
Ownfold signs you in with OPAQUE rather than sending a password (or a hash of one) to the server. That matters in four ways:
- The server never receives anything equivalent to your password.
- No salt is revealed before you authenticate, so nobody can precompute a dictionary for your account.
- A stolen database alone can’t start an offline guessing attack. That also needs the server’s OPRF seed, which lives on disk, apart from PostgreSQL.
- Argon2id parameters are pinned in client code. A malicious server can’t downgrade them to make your password cheap to crack.
Unknown email addresses get an indistinguishable fake response, so sign-in can’t be used to discover who has an account. Attempts are rate-limited per network address and per account.
Web sessions use an HttpOnly; SameSite=Strict cookie, and every state-changing request must also carry a custom header that cross-site pages can’t send. Only SHA-256 hashes of session tokens are stored.
Files and folders
Content
Each file version gets a fresh content key. The file is split into 4 MiB blocks, and each block is encrypted independently under a subkey, with associated data binding its index and whether it is the last block. Reordering, dropping, duplicating or truncating blocks is detected. Independent blocks also make uploads parallel and resumable, and allow random access within large files.
The server verifies each block’s SHA-256 as it arrives. The ordered list of block hashes is committed to in the version’s encrypted metadata, which the author signs with their Ed25519 identity key — so a collaborator who holds a folder key still can’t pass off a file as someone else’s.
Names and the tree
Every file and folder has its own random key. A node’s key and name are encrypted under its parent’s key, so access to a folder grants its subtree and nothing else. Every ciphertext binds the volume, node and parent, so the server can’t swap names between items or quietly move one; a real move re-encrypts the name and re-wraps the key.
To enforce “no two items with the same name in a folder” without learning names, clients send a keyed hash of each name. It reveals only whether two names in one folder are equal — to someone who can already see that folder’s listing.
Conflicts
Every upload states the version it was based on. If the file changed in the meantime, the server refuses the commit rather than overwriting, and the client can commit the same blocks as a conflict copy without re-uploading.
Public links
Links work for people without an account. The secret that decrypts the item travels in the URL fragment — the part after # — which browsers never send to any server.
https://<your-server>/s/<link-id>#<128-bit secret>
An optional password is enforced twice: by the server, which checks a derived token (rate-limited) before serving any ciphertext, and cryptographically, as an input to the link key. A server operator who learns the password check still can’t decrypt without the URL secret.
Links can expire, carry a download limit, allow or forbid downloads, and be revoked. Their activity log records only a /24 network (or /48 for IPv6) and a browser family.
File requests
A file request lets anyone drop files into one of your folders without being able to read it. Your device creates a dedicated X-Wing key pair for the request; the private key is wrapped under the folder’s key, and the public key — together with the title and message — is encrypted under a secret that lives only in the request URL’s fragment. Because uploaders get the public key from that authenticated blob, the server can’t substitute its own.
Each upload is encrypted exactly like any other file, with its key and name sealed to the request key. Your client later adopts it into the folder. Requests are bounded by expiry, per-file, total and count limits, your quota and rate limits.
Recovery
When you create an account, your device generates a 256-bit recovery key and shows it once. It wraps your master key independently of your password, so a forgotten password doesn’t mean lost files.
XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX
It’s written as 52 Crockford base32 characters plus a 4-character checksum, in groups of four. Typing is forgiving: case and separators are ignored, and O, I and L are read as 0, 1 and 1. The server stores only a hash of a value derived from the key — enough to authorize releasing the recovery-wrapped master key, not enough to open it.
You can issue a new recovery key at any time; the old one stops working.
On the server
- Ciphertext only. Storage accepts only data in the Ownfold envelope format, filed under random names unrelated to names or content. No storage provider can receive plaintext from the server, because the server never has any.
- Admins can’t read. Administrators manage accounts, quotas, invitations and health. No endpoint returns another user’s files, names or keys.
- Secrets apart from data. The OPAQUE server keys are generated on first start and kept on disk, outside the database. None of them can decrypt user data.
- A claimed install stays claimed. The first administrator needs a one-time setup token printed on the server, so nobody else on the network can claim a fresh install.
- Quiet logs. The audit log records security events and never contains names, content or key material. Change notifications carry identifiers and journal positions only.
- Strict browser headers. The web app ships with a restrictive Content Security Policy, no framing, no referrer, and HSTS over HTTPS.
Limits
Metadata
The server sees your email and display name, how many items you have, their sizes, how folders nest, when things change, and who shares with whom in which role. Sync, quotas and permissions depend on these.
Revocation
Revoking a share or link removes access from then on. It can’t make anyone forget files or keys they already obtained.
Code delivered by the server
In a browser, the encryption code comes from the server you connect to. The Content Security Policy narrows what injected content could do, but a server operator who turns hostile could serve altered code. This is a limit of all encryption in web apps; native apps built on the same core don’t depend on the server for their code.
Losing both secrets
If you forget your password and lose your recovery key, your files can’t be recovered by anyone. There is no reset that bypasses this — a reset would be a backdoor.
Status
Ownfold is in development, on cryptographic format version 1. Everything on this page is implemented in the encryption core and server. The web app that uses them is an early version, the Mac app is in progress, and the phone apps are planned. It hasn’t had an independent security audit yet. The post-quantum crates it relies on (ml-kem, x-wing) are newer and haven’t been independently audited either; they are pinned to exact versions and upgraded only after deliberate review.