ZK Key Security
DarkAuth’s current zero-knowledge model separates account custody from application delivery. The user has an Account Root Key, or ARK, stored inside a keybag. A ZK-enabled application receives a Client App Key, or CAK, derived for that client and organization context.
Keybag storage
Section titled “Keybag storage”The keybag stores encrypted envelopes for the ARK. Password login uses OPAQUE export-key material to unlock a password envelope. Passkey PRF, trusted-device, and recovery-key flows can provide additional envelopes. During honest operation, the backend and database cannot unwrap the ARK because they do not have the client-derived envelope keys.
Fragment delivery
Section titled “Fragment delivery”For current ZK-enabled clients, the app sends an ephemeral ECDH public key in the authorization request. Browser code unwraps the ARK locally, derives a CAK for the requesting client, encrypts the CAK to that key, and delivers the JWE through the URL fragment.
The server stores only zk_key_hash, a hash of the fragment JWE. The token response returns zk_key_hash, zk_key_kind, and zk_key_version so the app can verify that the fragment it received matches the authorization code before decrypting. Explicit legacy v1 clients use drk_hash, drk_jwe, and zk_drk_hash.
Session unlock
Section titled “Session unlock”DarkAuth unlocks the ARK once per sign-in, not once per tab. The approach follows Proton’s persisted sessions.
- After any unlock, the DarkAuth user UI encrypts the ARK with AES-256-GCM and stores only that ciphertext in
localStorage. - The key for that ciphertext is random, generated by the API, and kept in the server-side session for that one sign-in. It is never put in a cookie, log, or audit record.
- A new tab or reload fetches the key, decrypts the stored copy, and keeps the ARK in memory. No prompt is shown.
- Sign-out, sign-in expiry, revoking the sign-in from the portal, password reset, and SCIM deactivation delete the server key. The stored ciphertext is then useless and is removed.
- A new sign-in gets a new key, so an older stored copy cannot be decrypted.
The server holds the key but never the ciphertext. The browser disk holds the ciphertext but never the key. The ARK is only exposed to someone holding both while the sign-in is live.
The trade-off: a copy of the browser profile, including its cookies, can restore the ARK until that sign-in ends (at most seven days idle). Same-origin script can restore it at any time during the sign-in, not only while a page is unlocked. Memory-only custody would need the password again instead.
Admins can turn this off for SCIM-managed users with users.scim.allow_session_unlock. With it off, nothing is stored and each new tab needs an unlock method.
Custody recommendations
Section titled “Custody recommendations”Relying parties keep CAK and fragment-private-key custody memory-only. A reload in the app starts a new authorization, which DarkAuth can usually complete without a prompt. Clear delivered keys on logout. Remove the fragment from the URL after processing. Generate a fresh ephemeral key for each authorization request.
Persistent key storage in the app can improve convenience, but it weakens the hosted-web custody story. If you enable it, describe the tradeoff clearly.
Threats to consider
Section titled “Threats to consider”The ARK or CAK can be exposed by malicious app code, malicious DarkAuth frontend code, XSS, browser extensions, device compromise, or application code that logs or uploads delivered key material. The ZK flow reduces server-side custody; it does not remove frontend trust.