ZZPass Security Whitepaper
Last updated 2026-10-20.
ZZPass is an end-to-end-encrypted password manager for iOS and macOS. This document describes its trust model, cryptographic design, and the security properties of its sync, sharing, and time-locked-access features. It is written so that a security researcher can verify the claims against the source — the server components are open source (see Open source & verifiability) and vault encryption uses only standard primitives from Apple's CryptoKit and CommonCrypto.
We state limitations as plainly as guarantees. A password manager earns trust by being precise about what it cannot do, not only what it can.
1. Trust model
The design goal is zero-knowledge: ZZPass (the operator) and the sync transport (Apple iCloud/CloudKit) can never read your vault. All encryption and decryption happen on your device, under keys derived from your primary password, which never leaves the device.
What each party can see:
| Party | Can see | Cannot see |
|---|---|---|
| ZZPass operator | Nothing about vault contents. Our own servers hold ciphertext, timing/audit metadata, anonymous aggregates — and, for emergency access only, an owner-provided notification email while the deposit can still be denied (§6). | Passwords, notes, attachments, titles, URLs, keys, your primary password. |
| Apple / CloudKit | Encrypted record blobs, record counts, sync metadata, your Apple ID (for their own sync). | Plaintext of any field — everything sensitive is encrypted before it reaches CloudKit. |
| A network attacker | TLS-protected traffic only. | Plaintext (TLS + the payloads are already E2E-encrypted underneath). |
Consequences of zero-knowledge, stated honestly:
- If you lose your primary password, we cannot recover your vault for you. There are two ways back in, both in your hands: Face ID or Touch ID recovery on a device that's already set up, and your Emergency Kit (see §3).
- ZZPass cannot "reset" your account or read a shared item on your behalf.
- Security depends on the confidentiality of your primary password and the integrity of your device. A compromised device (malware, a jailbreak, physical access while unlocked) is outside the threat model — no client-side encryption can defend plaintext that a compromised OS can read once you unlock.
2. Cryptographic primitives
ZZPass implements no custom cryptography. It composes standard primitives from Apple CryptoKit and CommonCrypto:
| Purpose | Primitive |
|---|---|
| Password-based key derivation | PBKDF2-HMAC-SHA256, 650,000 iterations |
| Sub-key derivation | HKDF-SHA256 |
| Symmetric encryption (data at rest, sealed payloads) | AES-256-GCM (authenticated) |
| Public-key key agreement / key wrapping (ECIES) | P-256 ECDH |
| Digital signatures (group manifests, escrow deposits, update signing) | P-256 ECDSA (SHA-256) and Ed25519 (app-update signing) |
| Message authentication (escrow tokens) | HMAC-SHA256 (constant-time verification) |
| Hashing / identifiers | SHA-256 |
650,000 PBKDF2-SHA256 iterations exceeds current OWASP guidance (600,000). AES-256-GCM is authenticated, so tampering with a ciphertext is detected on decrypt.
3. Account keys and the key hierarchy
primary password ─PBKDF2-SHA256(650k, per-account salt)──▶ master key (AES-256)
│
┌─────────────────────────┼──────────────────────────┐
HKDF-SHA256 protects HKDF-SHA256
│ │ │
E2E identity key vault data (AES-256-GCM) other sub-keys
(P-256 keypair) via value transformers
- The master key is derived from your primary password with PBKDF2-SHA256 (650k iterations, a per-account random salt). It is held in memory while the app is unlocked and cached in the device Keychain with protection class
kSecAttrAccessibleWhenUnlockedThisDeviceOnly— not synchronizable to iCloud Keychain and inaccessible while the device is locked. - Vault data (credentials, notes, attachments) is encrypted with AES-256-GCM via Core Data value transformers (
CredentialTransformer/NoteTransformer), keyed by the master key / a vault-key ring. Data keys usekSecAttrAccessibleAfterFirstUnlockThisDeviceOnlyso background sync can run, still device-only and never exported. - The E2E identity key — a P-256 keypair used to receive shared items and escrow grants — is the source of truth stored (itself encrypted) in the account record, with a deterministic HKDF-SHA256 derivation from the master key as a pre-sync fallback. It is re-wrapped, not re-derived, on a master-password change, so it is stable across password changes (existing shares and grants keep working).
- Emergency Kit / Secret Key: at signup the app produces a printable Emergency Kit containing your email, your Secret Key (a readable encoding of the account salt), a field for your primary password — pre-filled when printed during signup — and a QR code carrying the same values for one-scan sign-in. The Secret Key never changes, even when you change your primary password. It is not only a recovery artifact: it is required every time you sign in on a new device (typed or scanned), where it feeds the PBKDF2 derivation together with your primary password. Recovery therefore needs Secret Key and primary password; a kit with the password recorded restores access by itself. Treat the printed kit like cash or a passport — with the password filled in, whoever holds the paper holds the vault. Besides Face ID or Touch ID recovery on a device that's already set up, the operator stores no key material, so there is no other path. Keep the kit safe and offline.
4. Data at rest and sync
Persistence is a dual Core Data + CloudKit store: a private store for your own vault and a separate shared store (.shared CloudKit scope) for shared groups. Every sensitive field is encrypted on-device before it is written to Core Data, so what CloudKit replicates is ciphertext. CloudKit sees encrypted blobs and sync metadata; it never sees plaintext or keys.
A credential or note belongs to either your private vault or a shared group, never both. Sharing does not move your original (see §5).
5. Secure sharing (E2E-encrypted shared vaults)
Shared groups let members see the same items without any party outside the group — ZZPass or CloudKit — being able to read them.
Per-group keys and key distribution
Each shared group has its own AES-256 group key. Shared items carry an E2E-encrypted JSON payload sealed under that group key. The group key is distributed to members by wrapping it to each member's P-256 public key using ECDH (ECIES) — only the holder of the matching private key can unwrap it. The operator and CloudKit only ever see the wrapped (ciphertext) form.
Copy-on-share
Sharing an item creates a linked copy: the private original stays in your vault, and a shared copy goes to the group, linked bidirectionally. Your plaintext original is never exposed to the group key; the shared copy is re-sealed under the group key.
Owner-signed group state and out-of-band verification
Group membership and the key ring are not taken on trust from whoever last wrote to CloudKit. ZZPass uses an owner-signed group manifest (P-256 ECDSA) that commits to the group's key-ring hashes and member roster; clients fail closed and refuse to adopt a ring that doesn't match the owner's signed commitment. This defeats a malicious co-member, a compromised server, or a network attacker trying to substitute keys or roster.
- Member-key pinning (TOFU): a member's public key is pinned on first use; a silent key substitution is rejected.
- Safety numbers: members can compare an out-of-band safety number to verify each other's identity keys directly (as in Signal), closing the trust-on-first-use gap. Once verified, an owner key is never silently overwritten.
- Key rotation on membership change: removing a member triggers a key-ring epoch rotation, so a removed member cannot read future content (their derivable key ring stops at their removal).
6. Time capsules and emergency access (zero-knowledge escrow)
Two features let a key reach someone later, under a condition: Time Capsules (release at a set date) and Emergency Access (a dead-man's switch — recipients gain access if the owner goes silent). Both are backed by one primitive: a server-enforced deadline on zero-knowledge-sealed keys, implemented by an open-source Cloudflare Worker (timelock-escrow).
Security properties of the escrow server:
- It cannot decrypt anything. It stores only key blobs already encrypted to the recipients' public keys, plus a release time. It returns opaque ciphertext after the deadline and never sees plaintext.
- Self-certifying identities.
user_id = SHA-256(P-256 public key). There is no account registry to squat, and the server contacts no external identity provider. A depositor addresses a grant to the hash of a recipient key it already holds; only the holder of the matching private key can mint an access token (via a signed possession proof). Tokens are HMAC-SHA256, verified in constant time. - The server clock is authoritative. Release timing cannot be spoofed by a client changing its own clock.
- Owner-signed deposits. Recipients verify a deposit's signature against the pinned group-owner key before adopting a released key ring, defeating malicious-member / server / MITM ring substitution.
- Out-of-band deny channel. For the dead-man's-switch case, arming a release can email the owner a signed link to halt or extend it — so they can stop an unwanted release without their primary device. The notification address is the one deliberate, bounded piece of plaintext PII the server holds: it must be able to send to it, it is the owner's own address, it is refused for timed capsules (which never arm), and it is scrubbed automatically once the deposit is revoked or released — after release the recipient's entitlement is crystallized (deny, extend, and revoke all refuse), so the address has no remaining purpose.
- Anti-enumeration & audit. Unknown and unauthorized capsule ids return the same 404 (a revoked capsule may additionally carry a signed absence attestation, so a recipient's client can distinguish deliberate revocation from server data loss without weakening the response for outsiders). Access requests and denials are recorded in an audit trail — including the requesting IP address, retained 180 days — and a released key's first retrieval is timestamped, so the owner can see who triggered a release; probes of a capsule before its release are answered but deliberately not logged.
The escrow server holds the one piece of state whose loss is unrecoverable in the dead-owner case; its disaster-recovery posture is documented in the timelock-escrow repository.
7. Autofill and browser integration
Credential AutoFill on iOS and macOS retrieves credentials through the on-device encryption boundary: plaintext is produced only in-process, only while the vault is unlocked, and is never transmitted.
A browser extension is in development and not released yet. Its source is already public (zzpass-browser-extension). It is designed to talk to a native messaging host on the same machine, so no vault data would cross the network in the clear.
8. Telemetry and privacy
Telemetry is anonymous, coarse, and opt-out, and its ingest server is open source (zzpass-telemetry) so the claims are verifiable:
- No account, name, email, IP, precise timestamp, or user-agent is stored. The client IP is used only to rate-limit and never reaches the database.
- Events are keyed by a client-generated random install id that maps to no user.
- It is on by default. Turning off Share anonymous usage data in the app's Security settings stops it and deletes anything not yet sent. Usage data never includes passwords, notes, names or email.
- Dimensions are coarse, bucketed values, strictly validated at ingest.
- With the same switch on, crash and hang reports from Apple's MetricKit (stack traces and device model, no vault content) are sent too.
- Data is short-lived: events are pruned after 180 days, diagnostic payloads after 90.
9. Threat model summary
Defended:
- A fully compromised operator / server (escrow and telemetry are zero-knowledge or anonymous; the private vault never reaches our servers at all).
- A compromised sync transport (CloudKit sees only ciphertext).
- A network attacker (TLS + already-encrypted payloads).
- A malicious or removed group member (owner-signed manifests, TOFU pinning, safety numbers, epoch rotation on removal).
- Replay / substitution of escrow grants (self-certifying identities, owner-signed deposits, server-authoritative clock, constant-time MACs).
Explicitly out of scope (assumptions):
- A compromised device / OS while the vault is unlocked.
- A weak or reused primary password (its strength bounds the whole hierarchy).
- Loss of the primary password when no Emergency Kit records it (unrecoverable by design) — the Secret Key alone cannot re-derive anything without the password.
- Traffic-analysis / metadata (record counts, timing) visible to the sync transport.
10. Open source & verifiability
The server components are published as standalone MIT-licensed repositories so anyone can audit exactly what runs:
timelock-escrow: the zero-knowledge time-lock / dead-man's-switch key escrow.zzpass-telemetry: the anonymous telemetry ingest server.zzpass-monitor: the uptime monitor.zzpass-browser-extension: the Chrome, Firefox and Edge extension, in development and not released yet.
The app itself is not open source. Vault encryption uses Apple CryptoKit and CommonCrypto, with no bespoke algorithms. Encrypted device-to-device connections use zzpass-core, a Rust library built on the open-source RustCrypto crates.
11. Reporting a security issue
We welcome reports from security researchers and treat them as a priority. If you believe you have found a vulnerability, please tell us before disclosing it publicly.
Contact. Email security@zzpass.com. Our machine-readable contact information is published at https://www.zzpass.com/.well-known/security.txt (RFC 9116). If your report is sensitive enough to need an encrypted channel, say so in a first plain message and we will arrange one. Please do not open a public GitHub issue for a security vulnerability.
What to include. The affected component and version, a description of the issue, steps to reproduce (a proof-of-concept if you have one), and the impact you believe it has. For a password manager we are especially interested in anything touching cryptography, key handling, the sharing or escrow protocols, or autofill.
Our commitments.
- We acknowledge every good-faith report within 3 business days.
- We give you an initial assessment within 7 business days and keep you updated as we work toward a fix.
- We aim to remediate confirmed vulnerabilities within 90 days — faster for high-severity issues — and will coordinate public-disclosure timing with you.
- With your permission we credit you in the release notes and a security-acknowledgements page.
Safe harbor. We will not pursue or support legal action against researchers who:
- make a good-faith effort to follow this policy,
- avoid privacy violations, data destruction, and degradation of service,
- access only their own accounts and data — never another user's, and
- give us reasonable time to remediate before disclosing publicly.
Activity conducted consistently with this policy is authorized; we consider it lawful, helpful, and appreciated.
In scope. The ZZPass iOS and macOS apps; the unreleased browser extension, whose source is public (zzpass-browser-extension), and its native-messaging host; the open-source server components (timelock-escrow, zzpass-telemetry, zzpass-monitor); and zzpass.com and its subdomains.
Out of scope — please don't report these without a concrete, demonstrated impact:
- Denial-of-service, volumetric, or rate-limit-exhaustion attacks.
- Social engineering, phishing, or physical attacks against ZZPass staff or users.
- Output from automated scanners without a working proof-of-concept.
- Missing "best-practice" hardening (headers, SPF/DMARC nuances, TLS configuration) with no demonstrated exploit.
- Vulnerabilities in third-party services we rely on (Apple, Cloudflare, our email provider) — report those to the respective vendor.
- Self-XSS, or issues that require an already-compromised or jailbroken device (see the threat model in §9).
A machine-readable summary is published at https://www.zzpass.com/.well-known/security.txt (RFC 9116).
This whitepaper is a living document; it should be updated as the design evolves. Where this document and the source disagree, the source is authoritative — please report the discrepancy.