Zero-Knowledge Fitness Tracking: How VitaLog Actually Protects Your Data
Most fitness apps say "your data is encrypted." Few can say "even we can't read it." VitaLog is in the second category, here's exactly how, and what it means for you.
The problem
Fitness and health data is uniquely sensitive. Progress photos, weight, body composition, bloodwork, mood, sleep, nutrition, and supplement use: together they paint a picture of your private life most advertising platforms would pay a great deal to access. The dominant apps in the category have turned that access into their business model:
- MyFitnessPal: acquired by Under Armour in 2015, shared data with third parties, suffered a 150-million-record breach in 2018.
- Fitbit: acquired by Google in 2021; data flows into ad-targeting systems.
- Strava: global heat maps of runs inadvertently revealed military base locations in 2018.
- Various calorie trackers: routine sharing with Meta, Google, and ad networks via embedded SDKs.
The failure mode isn't always malicious. It's that plaintext-on-server gives every party downstream (advertising integrations, law enforcement, insurance companies, future acquirers) the technical capability to read your data. Architecture is policy. An app that can read your data eventually will, either by compromise or by choice.
What zero-knowledge actually means
Zero-knowledge is a specific, testable property: the service provider holds no information capable of decrypting user data. Keys live only on client devices, encryption happens client-side before upload, and the server holds only ciphertext.
The test: if an attacker fully compromised our servers, could they read your photos? For a zero-knowledge system, the answer is no, they would have ciphertext and opaque metadata, but no key. The same test applies to a government subpoena, an acquisition by a less scrupulous company, or a rogue engineer. In all cases, the answer must be no.
"Encrypted at rest" is NOT zero-knowledge. Neon Postgres encrypts our database at rest, but Neon holds the decryption keys, so Neon can decrypt, we can request decrypted queries, and anyone who compromises Neon can decrypt. "Encrypted in transit" is NOT zero-knowledge either, TLS protects data as it travels but unencrypted data lives on the server once it arrives. Both are necessary but neither is sufficient.
Zero-knowledge adds a client-side encryption step before upload, where the key is a secret only your device holds.
How VitaLog implements it (photos)
When you enable photo cloud sync and upload a photo, the following happens entirely on your device:
- Compress. Your photo is resized to 800 px longest edge, re-encoded as JPEG at quality 0.7. No EXIF is persisted.
- Chunk. The compressed JPEG is sliced into 1 MB chunks. Each chunk is independent.
- Encrypt. Each chunk is encrypted with AES-256-GCM. A 12-byte IV is generated for each chunk (cryptographically random, never reused). The IV is prepended to the ciphertext. Authentication tag length is 128 bits.
- Upload. Each ciphertext chunk is PUT to a dedicated R2 URL like
photos/{userId}/{photoId}/chunk-{i}. The server never sees the plaintext, the IV separately, or the encryption key. - Commit. After all chunks are present, a final commit marks the photo as complete. The server verifies chunk presence and total byte count, both of which are visible metadata, but cannot verify anything about the content.
The encryption key itself is:
- 256-bit AES-GCM: generated via Web Crypto API (
crypto.subtle.generateKey). - Per-user (not per-photo, so cross-device decryption is practical) but unique to your account.
- Stored non-extractable in IndexedDB on your device, browsers enforce this flag at the crypto-subtle layer so the raw key bytes are never exposed to application code.
- Wrapped for backup under PBKDF2(password, random salt, 600,000 iterations) and AES-256-GCM. Only the wrapped form is sent to our server. Without your password, unwrapping the key is computationally infeasible, even with unlimited compute, the password space is the bottleneck, and password-strength + 600k iterations gives cost well above any realistic attacker's budget.
For OAuth accounts (Google, Apple sign-in) where we never have a password to derive from, you set a separate photo passphrase in Settings that plays the same role. The passphrase wraps the photo key; we hold only the wrapped form. Losing the passphrase loses cross-device photo access but not the account itself.
What is NOT zero-knowledge (and why)
For transparency: not every data path in VitaLog is end-to-end encrypted. Here's the honest breakdown:
| Data | Encryption model | Why |
|---|---|---|
| Progress photos | Zero-knowledge (client-side AES-256-GCM, per-user key, server never holds key) | Highest sensitivity; embarrassment + identification risk |
| Structured state (protocols, journal, bloodwork, workouts, nutrition, settings) | Local-first by default. Cloud sync is TLS in transit + Neon Postgres encryption at rest | Cloud sync is opt-in; the vault option wraps it client-side if you want full E2E |
| Account data (email, display name, password hash) | TLS in transit; PBKDF2-hashed password; Neon at-rest encryption | Required for authentication; zero-knowledge password auth (SRP, OPAQUE) is a possible future upgrade |
| Wrapped encryption keys | Stored on server but wrapped with PBKDF2(password), server has ciphertext only | Enables roaming between devices after the user authenticates |
| Optional vault backup | Full state AES-256-GCM wrapped under PBKDF2(password) | Opt-in for users who want full state E2E-encrypted |
| Real-time sync notifications | Only metadata, HLC timestamps, device labels, never state content | The Durable Object fanout carries pointers, not payloads |
Why not make every path zero-knowledge? Engineering tradeoff. Fully client-side search, trend detection, and compound-interaction analysis would slow the experience significantly for the average user. The photo path is where the worst-case exposure is most severe, so that's where we invested in the strictest guarantee. The structured-data path has less sensitive worst-case exposure and the opt-in vault feature serves users who want the full E2E model.
How this compares to other fitness apps
| App | Progress photo encryption | Ads / third-party trackers | Breach history |
|---|---|---|---|
| VitaLog | Zero-knowledge (client-side AES-256-GCM) | None, ever | None |
| MyFitnessPal (Under Armour) | Plaintext on server; at-rest encryption only | Ads throughout app; data shared with advertising partners | 2018: 150 million records breached |
| Hevy | Plaintext on server | Freemium; some analytics integration | None publicly known |
| Strong | Plaintext on server | Freemium; analytics integration | None publicly known |
| Cronometer | Plaintext on server | Freemium + Gold tier | None publicly known |
| Fitbit (Google) | Plaintext on Google servers | Part of Google ad-targeting ecosystem | None for Fitbit itself |
| Apple Health | iOS data-protection + iCloud end-to-end (iCloud Keychain) | No ads; no third-party trackers | None |
Apple Health is the closest peer on the encryption dimension, Apple's iCloud end-to-end-encryption model for Health data is genuinely zero-knowledge for Apple-ecosystem users. VitaLog's advantage over Apple Health: multi-platform (iOS, Android, Windows, macOS, Linux), pharmacology/peptide/bloodwork/interaction analysis that Apple Health doesn't provide, and the portable single-JSON export for users who want their data in one file.
What law-enforcement requests look like
If VitaLog receives a valid subpoena or warrant for a user's account, we can produce:
- Account metadata (email, display name, account creation date)
- Login history (IP hashes, user-agent summaries) from the most recent 90 days of audit logs
- Photo ciphertext chunks and the wrapped encryption key (both useless without the user's password)
- Structured-state JSON (unless the user enabled the opt-in vault, in which case we produce ciphertext only)
We cannot produce:
- Photo plaintext
- User passwords (stored as PBKDF2 hashes only)
- Photo encryption keys in unwrapped form
- Vault-encrypted state in plaintext if the vault option was enabled
This is not a legal loophole or clever phrasing. It is architectural. The data is mathematically unavailable to us without the user's password or passphrase.
Why you can trust this claim
Architectural security claims are only believable if verifiable. VitaLog's are verifiable via:
- Open source-level transparency on the client: the encryption code runs in your browser, not a black box server. In DevTools → Sources, inspect
photo-upload-queue.jsandvault-photo-key.jsdirectly (they load unminified in development builds; the production bundle is minified but still JS-readable). In DevTools → Network, watch a photo upload and confirm the chunks PUT to R2 are high-entropy binary (ciphertext) rather than a JPEG header. Any drift from the claim is visible in your own browser. - Strict Content-Security-Policy: our CSP forbids third-party scripts, inline script execution, and eval. No external ad-tech or analytics can silently intercept your encryption key.
- HSTS preloaded: TLS downgrade attacks cannot serve a malicious JavaScript bundle.
- Public DPIA + RoPA: our Data Protection Impact Assessment and Records of Processing Activities document what we process and how. Available on request.
- Right to export + delete: single-click JSON export (Article 20); account deletion propagates in 30 days.
If we ever weaken this model, for example by capturing decryption keys on the server, a visible code diff would show it. The architecture is the commitment.
Try VitaLog
Free. No ads. Zero-knowledge photos. Real-time multi-device sync. Everything client-side unless you explicitly opt into cloud sync.
Open VitaLog Live demoFrequently asked questions
- What does "zero-knowledge" actually mean?
- Zero-knowledge means the service provider (in this case VitaLog) holds no information capable of decrypting your data. Keys live only on your device, the encryption happens before anything leaves your device, and the server stores only ciphertext. A server compromise, insider threat, or legal subpoena cannot produce plaintext because we fundamentally do not have it.
- Is the entire app zero-knowledge?
- Progress photos are zero-knowledge end-to-end. The rest of your state (protocols, journal, bloodwork, workouts, nutrition) is local-first by default, if you never enable cloud sync, nothing leaves your device. Optional cloud sync for that state uses TLS in transit and encryption at rest on our database (Neon Postgres). The separate opt-in vault feature encrypts that state with a PBKDF2-derived key from your password for full end-to-end protection. Photos are the strictest tier because they are the most sensitive.
- How does VitaLog encrypt photos?
- When you upload a photo: (1) client compresses to 800 px max, (2) slices into 1 MB chunks, (3) encrypts each chunk with AES-256-GCM using a unique 12-byte IV per chunk, (4) uploads ciphertext to Cloudflare R2 storage. The encryption key is 256-bit AES-GCM, generated on your device and stored in IndexedDB with "non-extractable" flag set. For password accounts the key is wrapped under PBKDF2(password, salt, 600,000 iterations) and the wrapped version is stored in our database, we store the wrapped key but cannot unwrap it without your password. For OAuth accounts, you set a separate photo passphrase that plays the same role.
- Can VitaLog comply with a subpoena for photo content?
- No, we are architecturally incapable of producing plaintext. We can produce only (a) the ciphertext chunks and (b) opaque metadata (photo ID, user ID, chunk count, upload date, user-chosen category label). The ciphertext is indistinguishable from random bytes without the decryption key. We can fulfill preservation orders and metadata requests but cannot decrypt the content.
- What happens if I lose my password?
- If your account is password-based and you have no other device with the cached key, you lose access to existing cloud-stored photos. The ciphertext remains in R2 storage, but there is no recovery path on our side because we never held the key. Account data not in photos (protocol, journal, bloodwork) is recoverable via password reset. If you are on an OAuth account and forget your photo passphrase, the same applies, only photo access is lost, not your account.
- How does this compare to MyFitnessPal?
- MyFitnessPal stores your data in plaintext on their servers, shares it with advertising partners, has been breached (2018, 150 million records), runs ads throughout the app, and offers a "Premium" tier for features like food scanning that are free elsewhere. Your food, weight, exercise, and photo data are products for their advertisers. VitaLog stores progress photos as ciphertext we cannot read, runs zero ads, and embeds no third-party trackers.
- How does this compare to Hevy or Strong?
- Hevy and Strong are excellent workout-focused apps but store your data in plaintext (beyond transport TLS and database encryption-at-rest). Neither offers client-side encryption for photos or journal. Both collect analytics and, in some versions, run ads. VitaLog's zero-knowledge photo sync is the distinguishing feature.
- Does Apple Health protect my data?
- Apple Health data on-device is protected by iOS data-protection APIs (encrypted with the device passcode). iCloud sync of Health data is end-to-end encrypted with your iCloud keychain. This is a strong privacy model for Apple-ecosystem users. VitaLog complements Apple Health with pharmacology, bloodwork, peptide tracking, and cross-device sync for Android + web that Apple Health doesn't cover. If you're iOS-only and want only Apple's standard features, Apple Health plus a simple tracker may be all you need; VitaLog serves the broader set of clinical-grade tracking use cases across platforms.
- Why not make everything zero-knowledge?
- Engineering tradeoff. Zero-knowledge requires client-side search, client-side trend detection, client-side all-the-things, which is feasible but slow for very large data sets. The progress-photo path is where plaintext-server-access has the worst consequences (identification, embarrassment, advertising exploitation), so that's where we invest in full zero-knowledge. The structured-data path (numbers, dates, labels) has less sensitive worst-case exposure. For users who want everything encrypted, the opt-in vault feature wraps the full state in client-side AES-256-GCM with a password-derived key.
- What about sub-processors like Cloudflare and Neon?
- Cloudflare R2 holds encrypted photo ciphertext, the encryption happens before upload, so Cloudflare sees random bytes. Neon Postgres holds account data, wrapped encryption keys, and structured state; data is encrypted at rest by Neon, in transit by TLS, and the vault-encryption option wraps it client-side before upload for strict zero-knowledge on the structured path too. Resend sends transactional email (verification, password reset). All sub-processors are listed in our sub-processors document and sign EU 2021 Standard Contractual Clauses.
Related resources
- Privacy Policy, complete data-handling disclosure
- TRT bloodwork guide
- Peptide dose calculator