Most "zero-knowledge" apps claim it in marketing. Few actually implement it.
True zero-knowledge means: the server cannot decrypt your data, even if it wanted to. Even if law enforcement subpoenas it. Even if the server gets hacked. The server sees only encrypted bytes.
This isn't theoretical. CHRONOS implements it. Here's how.
Zero-knowledge isn't a feature. It's an architecture. Build it from the beginning or don't build it at all.
The core principle: Encryption before transmission
Zero-knowledge has one rule: plaintext never leaves the client.
Here's what happens:
Zero-knowledge data flow
The critical insight: Vercel never holds the decryption key. The key stays on the user's device. The server cannot decrypt even if it tries.
The technical implementation: How CHRONOS does it
Here's the actual code pattern:
// 1. Client-side encryption (happens BEFORE sending)
import { webcrypto } from 'crypto';
async function encryptVault(plaintext, userKey) {
// userKey = 256-bit key (only user has this)
// AES-256-GCM: authenticated encryption (prevents tampering)
const iv = webcrypto.getRandomValues(new Uint8Array(12));
const encrypted = await webcrypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
userKey,
new TextEncoder().encode(plaintext)
);
// Return encrypted blob + IV (IV is public, doesn't compromise security)
return {
iv: Array.from(iv),
ciphertext: Array.from(new Uint8Array(encrypted))
};
}
// 2. Send encrypted blob to Vercel (plaintext never transmitted)
const encrypted = await encryptVault(userThought, userKey);
const response = await fetch('/api/vault', {
method: 'POST',
body: JSON.stringify(encrypted) // Only ciphertext
});
// 3. Server stores encrypted blob (never sees plaintext)
// Vercel API handler
export default async function handler(req, res) {
const { iv, ciphertext } = req.body;
// Store as-is. Server cannot decrypt.
await db.vaults.create({
userId: req.user.id,
iv, ciphertext,
createdAt: new Date()
});
return res.json({ success: true });
}
// 4. Client-side decryption (only on user's device)
async function decryptVault(encrypted, userKey) {
const decrypted = await webcrypto.subtle.decrypt(
{ name: 'AES-GCM', iv: new Uint8Array(encrypted.iv) },
userKey,
new Uint8Array(encrypted.ciphertext)
);
return new TextDecoder().decode(decrypted);
}
// User retrieves vault
const vault = await db.vaults.findById(vaultId);
const plaintext = await decryptVault(vault, userKey);
// Only now is plaintext visible (in browser)
That's it. The pattern is:
- User enters plaintext
- Encrypt client-side with user's key
- Send only ciphertext to server
- Server stores ciphertext
- When retrieving, client decrypts
The server never touches plaintext. Ever.
If the server never sees plaintext, it cannot leak it, sell it, or be compelled to hand it over.
The key problem: Where does the encryption key live?
This is the hardest part of zero-knowledge architecture.
You need a cryptographic key to encrypt/decrypt. Where should it live?
CHRONOS uses Option 1 (password-derived key) for simplicity + Option 2 (browser storage) for convenience. The tradeoff: passwords are the weakest link.
This is why CHRONOS emphasizes: use a strong, unique password. Your encryption is only as strong as your password.
Building zero-knowledge on Vercel: The architecture
Here's how to structure the app:
Client (React / Frontend)
Where encryption happens. User writes plaintext. Client-side JS encrypts before sending. Client decrypts on retrieval. Plaintext never leaves this layer except within the user's browser memory.
API (Vercel Functions)
Stateless endpoints. Receive encrypted blob. Store blob. Retrieve blob. Return blob. Never touches plaintext. Can be written in Node, Python, Go โ doesn't matter. It doesn't see secrets.
Database (Postgres, MongoDB, etc.)
Stores encrypted blobs and metadata. Never stores keys. Never sees plaintext. Could be compromised; attackers get random bytes, not secrets.
Authentication Layer
Verifies user (JWT, sessions). But does NOT encrypt/decrypt. API layer just checks "is this user allowed to access this vault?" then returns encrypted blob to client.
The key principle: Encryption/decryption logic is client-only. The server is stateless about plaintext.
The honest trade-offs: What zero-knowledge costs
Zero-knowledge is powerful but expensive:
Trade-off #1: No Server-Side Search
With encrypted data, the server cannot search your vaults. It can't find "all entries from January" or "all entries containing 'anxiety'." You can only search on the client. This limits UX (search is slower, only works on downloaded data).
Trade-off #2: No Server-Side Sync Without Trust
When you sync across devices, encrypted data must match. But "match" is hard to verify when you can't decrypt. CHRONOS solves this with device isolation + timestamps, but it's complex.
Trade-off #3: Metadata Still Exists
Server sees: when you write, how often, file sizes, device info, IP address. This metadata alone reveals patterns (behavior, location, emotional state changes). Zero-knowledge encrypts content but not metadata.
Trade-off #4: Complexity for Developers
Encryption/decryption logic is tricky. Mistakes are catastrophic. Key management is hard. Most teams don't build this correctly. Zero-knowledge requires serious engineering discipline.
Despite these costs, the benefit is profound: your data is yours, not the platform's.
The cryptography: What makes it actually work
Zero-knowledge relies on three cryptographic primitives:
- AES-256: Symmetric encryption. Fast. Unbroken after 25 years. If key is secret, ciphertext is unbreakable.
- GCM (Galois/Counter Mode): Authenticates ciphertext. Detects tampering. Prevents attacker from modifying encrypted data without being caught.
- PBKDF2/Argon2: Derives encryption key from password. Slows down brute-force attacks. Argon2 is newer, stronger.
Together: AES-256-GCM with PBKDF2 key derivation is the industry standard for zero-knowledge apps. It's what Signal, Telegram (optional), and CHRONOS use.
The security is proven. The weakness is always the human side: password strength, key management, device security.
Implementation checklist: Building zero-knowledge correctly
If you're building zero-knowledge:
Use a proven library
Don't implement encryption yourself. Use libsodium (Sodium.js), TweetNaCl.js, or Web Crypto API. Let cryptographers handle the hard parts.
Never log plaintext
Not in server logs, not in error messages, not in monitoring. If plaintext appears anywhere server-side, you've broken zero-knowledge.
Encrypt before sending
Always. No exceptions. If you ever transmit plaintext, zero-knowledge is broken. Make encryption the default step before any HTTP call.
Use strong key derivation
PBKDF2 with 600,000 iterations, or Argon2. Weak key derivation is the #1 way zero-knowledge fails.
Audit for metadata leaks
Even with encryption, metadata can reveal patterns. Think about what the server learns: timestamps, file sizes, sync patterns, device info. Minimize what's logged.
Test the failure case
What happens if the encryption key is forgotten? Make sure data isn't unrecoverable. Plan for key recovery (backup codes, device syncing).
This checklist is not exhaustive. Building privacy-respecting systems is hard. Hire security experts if you're handling sensitive data.
Why Vercel? Why serverless?
Zero-knowledge can be built on any backend, but serverless (Vercel Functions) has advantages:
- Stateless by default: Serverless functions don't retain state between calls. Less risk of accidental plaintext retention.
- No persistent infrastructure: No servers sitting around with secrets. Each request is a fresh process.
- Simple scaling: If you need to scale, serverless scales automatically. No infrastructure to manage.
- Cost: You pay only for execution, not idle servers. Zero-knowledge apps that store encrypted data don't need much server compute.
That said, zero-knowledge can run on traditional servers (AWS EC2, DigitalOcean, your own hardware). The architecture is backend-agnostic.
Zero-knowledge isn't about the platform. It's about the principle: plaintext never leaves the client.
The shift in software design
Zero-knowledge represents a fundamental shift in how we think about user data:
- Old model: "Users trust us with their data. We'll be good stewards."
- New model: "Users' data is theirs. We literally cannot read it."
The new model doesn't require trust. It requires architecture.
This is why CHRONOS was built zero-knowledge from day one. Not because we're unusually ethical, but because the architecture is becoming table-stakes for apps handling sensitive data (journals, health records, financial data).
If you're building an app that stores user secrets, build it zero-knowledge. If you can't, acknowledge it. Don't claim privacy you don't have.
CHRONOS
Built zero-knowledge
from day one.
Your vault is encrypted on your device. We literally cannot read it. That's not a promise. It's an architecture.
Open CHRONOS