Your password never leaves your device: how login works
A password proves who you are and unlocks your keys. If one secret does both, the server sees the key to your files. How Sapherbox derives two keys from one password.
Every end-to-end encrypted product asks you for a password, and that password has two jobs that pull in opposite directions.
The first job is proving to the server that you are you, so it hands you a session and lets you fetch your (encrypted) files. For that job the server has to check something you send it.
The second job is unlocking your keys. In Sapherbox, your password is turned on your device into the key that opens your Account Master Key, and from there every file, folder and group key you have access to. For that job the server must never see anything that could do the unlocking.
If one secret does both jobs, the second job loses. Whatever you send for login is exactly what unlocks your keys, and a server that is compromised, subpoenaed or simply curious can hold on to it. Most products with “zero knowledge” in the brochure have this gap somewhere, and it is worth asking any vendor where the password goes.
This article explains how Sapherbox gives each job its own secret, both derived from the one password you type, so that the thing the server checks cannot open anything.
Two keys from one password
When you type your password, your browser (or the sync client, or the Mac app) does the following, in this order.
Step 1: a slow key derivation. The password and a random salt that was created at registration go into Argon2id, a deliberately expensive function. It uses 64 MB of memory and three passes, which is a fraction of a second on your device and years of work for someone guessing passwords at scale. The result is a 256-bit value we call the wrap key. It is the key that unwraps your Account Master Key. It stays on your device, always.
Step 2: a one-way step. The wrap key goes through HKDF, a standard key derivation function, with a fixed label. The result is a second 256-bit value, the login key. This is the only thing sent to the server.
The important property is in the direction of the arrows. The wrap key produces the login key, but the login key cannot be turned back into the wrap key. HKDF is built on SHA-256, and reversing it is as hard as reversing the hash. Knowing the login key tells you nothing about the key that opens your files.
password + salt --Argon2id--> wrap key (unlocks your master key, stays on the device)
wrap key --HKDF-----> login key (sent to the server, proves who you are)
Step 3: the server checks the login key. It does not store the login key either. It stores an Argon2id hash of it, with its own salt, exactly as a well-run service stores password hashes. At login it hashes what you sent and compares.
There is one small step before all this. The client first asks the server for the account’s salt and Argon2id parameters, because without them it cannot derive anything. For an email address that has no account, the server answers with a stable, realistic-looking decoy, so that question cannot be used to find out which addresses are registered.
What that buys you
Think about what each party holds after this design.
Your device holds the password for a moment, the wrap key for as long as you are unlocked, and the login key for the duration of one request.
The server holds a hash of the login key, the master key wrapped under the wrap key, and the identity keys wrapped under the master key. Three locked boxes and one fingerprint of a key that opens none of them.
So consider the cases that matter to a security reviewer:
- A copy of our database is stolen. The attacker gets hashed login keys. Each login key is 256 random-looking bits, so there is nothing to guess; the hash is a dead end. The wrapped master keys are AES-GCM ciphertext under keys the attacker does not have. Your files stay closed.
- The server itself is compromised while running. An attacker inside the server can see login keys arriving. That lets them impersonate users to the API and download ciphertext, which is bad and which the audit trail is built to expose, but it does not let them decrypt a single file. Before the split, the same position would have yielded the password, and with it the wrap key.
- A malicious operator. Us, in other words, or someone with our access. The same answer applies. We designed the system so that we cannot be asked for your password, because we never receive it.
A side effect worth mentioning: password rules are now enforced on your device, before anything is derived. The server cannot check that a new password has an uppercase letter, because it only ever sees a key. The client fetches the tenant’s policy and refuses to continue if the policy cannot be loaded, rather than silently relaxing it.
What it does not buy you
We publish our limitations, so here are the ones that apply.
The code that runs on your device sees the password. That is unavoidable; something has to run Argon2id. The question is whether you can trust the client you received. For the web app that means trusting the code we serve, protected by TLS and a strict content security policy; for the desktop and Mac clients it means signed and notarized builds. Client delivery is the part of the trust chain we still owe you the most on, and we say so in our threat model.
A phished password is a phished password. If you type it into a site that is not Sapherbox, the split does not help. Passkeys do: a passkey login never involves the password at all, and it is bound to our origin. If you can, use one.
A compromised device is game over. The wrap key and the master key live on your device while you are unlocked. Malware there reads your files as you do. No encryption design can change that, which is why we do not try to replace endpoint protection but work alongside it: we partner with leading endpoint security companies so that our customers can combine Sapherbox with the device protection they already run.
Rolling it out without a password reset
Here is the awkward engineering part. On the day the split went live, every existing account had a hash of its plaintext password on the server. The server cannot convert those into hashes of login keys, because it does not have the wrap keys. Forcing every user through a password reset was not an option either.
The solution is an upgrade on login. Each account carries a small marker saying which scheme its stored hash uses. When a client asks for the salt before logging in, the server also says which scheme the account is on. An old-scheme account sends its password one last time, over TLS; the server verifies it against the old hash, derives the login key itself this once, stores the new hash, and never accepts the password again. From the next login on, only the login key works.
We used the same mechanism a second time, a week later, when the product became Sapherbox and the HKDF label changed from the old name to the new one. A server cannot convert those hashes either, so a client that sees an account on the old label sends both the old login key and the new one; once the old one verifies, the new one replaces it. Nobody was logged out and nobody reset anything.
All three clients, the web app, the sync client and the Mac app, derive the key the same way, and a shared test vector in each code base makes sure they stay byte-identical. For a reviewer who wants to check:
password "correct horse battery staple"
salt 00 01 02 … 1f (32 bytes)
Argon2id m = 65536 KiB, t = 3, p = 4, 32-byte output
HKDF SHA-256, empty salt, info "sapherbox-login-v1", 32-byte output
login key s4U3a+VCYzwCj8xmwk4VelMMrZsd7TANEmm1PYf2sAg= (base64)
Questions?
If your security team wants the full derivation and the server-side verification described in one place, the encryption-flow document behind this article is available on request at hello@sapherbox.com.