Skip to content
Skip to content
Get started
/Security

Security

Passphrase & key management

zcrypt keeps two secrets cleanly separate: the passphrase that unlocks your encryption, and the password that authenticates your account. This page traces both, and the defenses around the sessions in between.

Two different secrets

It is worth getting this distinction straight, because the two secrets do very different jobs and are handled very differently.

Vault passphraseAccount password
PurposeDerives your encryption keysAuthenticates login
Reaches the server?NeverVerified, then only a hash is stored
Stored anywhere?No: device memory onlyAs a bcrypt hash
If forgottenData is unrecoverableReset via email

They can be the same string, but they are not the same secret

Even if you choose to type the same value for both, they travel different paths: the passphrase is consumed locally by key derivation, while the password is checked server-side. One unlocks cryptography; the other unlocks an account session.

The vault passphrase

Your vault passphrase is the root of all encryption in zcrypt. It is fed through PBKDF2-HMAC-SHA256 at 600,000 iterations with a per-file salt to derive the Key Encryption Key that wraps each file's content key. The full mechanics are in the encryption model.

  • •It is never transmitted to the server and never stored, not even hashed.
  • •It lives only in your device's memory while the vault is unlocked, and is discarded when the session ends or the vault locks.
  • •Because the server holds nothing derived from it, a strong passphrase is the single most important thing you control. New vaults require at least 10 characters; choose a long, unique one and keep it in a password manager.

Newer clients can also wrap your file keys under one random account master key, itself wrapped under your passphrase-derived key, so a device derives one key per unlock instead of one per file. Writing files this way is off by default and only the instance operator can turn it on; files stay readable either way. Losing the passphrase makes the master key unrecoverable too.

There is no passphrase reset

Recovery would require the server to hold a copy of your key, which would defeat zero-knowledge. If you lose your passphrase, your encrypted data cannot be recovered by anyone, including us. This is the deliberate cost of the guarantee.

Your sharing keypair

To share with another zcrypt user without ever handing a usable key to the server, every account is bootstrapped with its own X25519 keypair. It is not a third secret for you to remember: it rides on your passphrase, the same way your file keys do. The keypair is generated on your device the first time an unlocked session needs it.

  • •The public key is published to a small registry the server keeps in the clear, alongside a short fingerprint, so other members can wrap a shared key to you. It reveals nothing sensitive. That is what public keys are for.
  • •The private key never leaves your device in the clear. It is wrapped under your passphrase-derived key using the same PBKDF2 → AES-256-GCM envelope as your file keys, and only that ciphertext is stored. The server cannot unwrap it.
  • •Unlocking rehydrates it locally. When you unlock, the client fetches your wrapped private key and opens it with your passphrase in memory; if you have never published one, it generates and publishes a keypair on the spot.

Because your private key is decrypted into the browser while you are unlocked, a cross-site-scripting bug becomes the threat that matters most. As defense-in-depth against it, the web app serves a strict Content-Security-Policy in production. It is enforced: the browser blocks anything the policy does not allow. (A self-hoster can downgrade it to report-only with CSP_REPORT_ONLY=1.) It is a second line of defense, not a guarantee: the web app still runs the JavaScript the site serves you, so the desktop app is the stronger option if that trust matters.

Fingerprints defend against a swapped key

Because the registry is served by us, a dishonest server could in principle hand a collaborator the wrong public key and interpose itself. Each key carries a short SHA-256 fingerprint you can compare out of band (in person, over a call) to catch exactly that. Your browser computes the fingerprint itself from the key, pins it the first time you see a collaborator’s key (trust on first use), and asks you to confirm if that key later changes. The sealing mechanics (an ephemeral-ECDH ECIES sealed-box) are in the encryption model.

It shares the passphrase's fate

Since the private key is wrapped under your passphrase, losing the passphrase means it can no longer be unwrapped either: consistent with the rest of zero-knowledge: nothing derived from your passphrase is recoverable without it.

Account login security

Password hashing

Account passwords are hashed with bcrypt at cost factor 12 before storage. bcrypt is deliberately slow and salted per-hash, which blunts offline cracking if the database is ever exposed.

Breach screening (HaveIBeenPwned)

At registration, your chosen password is checked against the HaveIBeenPwned database using k-anonymity: only the first five characters of its SHA-1 hash are sent, never the password itself. If it appears in known breaches, you get a warning with the count. This warns rather than blocks, and it fails open, if the breach service is unreachable, signup is not held up.

Two-factor authentication (TOTP)

You can enable time-based one-time passwords (TOTP, RFC 6238) with any standard authenticator app. Once enabled, login issues only a short-lived intermediate token until the correct code is supplied: the full session token is never minted on password alone.

Tokens & sessions

Sessions use signed JSON Web Tokens, with several deliberate hardening choices around them.

  • •HS256 with an explicit algorithm check. The verifier requires the header algorithm to be exactly HS256 and rejects anything else, closing the classic algorithm-confusion (e.g. alg: none) attack.
  • •Typed tokens. The short-lived token issued mid-login for the 2FA step is a distinct type and is refused at authenticated endpoints, so it can never be replayed past the second factor.
  • •Short-lived access tokens. Access tokens expire quickly and are refreshed via a longer-lived refresh token.
  • •Refresh tokens are stored hashed. Only a SHA-256 hash of each refresh token is kept, and the issuing IP address (coarsened to its /24 for IPv4 or /48 for IPv6) and user-agent are recorded, and a change of network is flagged in the logs.

Revocation & recovery

Every account carries a token version embedded in its access tokens. Bumping that version instantly invalidates every outstanding token for the account, a single switch to sign out all sessions everywhere, used on password change and on demand.

  • •Account recovery (forgotten password) goes through an emailed, single-use reset token: the hash of which is what we store, never the token itself.
  • •Vault recovery (forgotten passphrase) does not exist by design; see above. Account access and data access are separate problems.
  • •Refresh tokens can be revoked individually or wiped for an entire account, and all of them expire on a fixed schedule regardless.

Login does not unlock your vault

Authenticating proves who you are to the service; it does not hand the server your keys. After login you still supply your passphrase locally to decrypt: which is why even a fully compromised account session cannot read your files without it.

Where to go next