Zero-Knowledge Architecture

"The server was hacked. Nothing was exposed."

OWASP (The Open Web Application Security Project) France chapter at Theodo offices, May 18th

Related Incidents

Pattern: the server knew too much

Company Year What the attacker got
LinkedIn 2012 117M hashed passwords (no salt)
Adobe 2013 153M passwords + plaintext hints
RockYou 2009 32M passwords in PLAINTEXT
LastPass 2022 Encrypted vaults + master pw hints
Pattern: the server knew too much. So the attacker got everything.

The Flawed Mental Model

"The server encrypts before storing. We're safe."

User
plaintext
SERVER (sees everything)
encrypts
DB
one breach = game over
The problem is not storage. The problem is that the server sees plaintext at all.

The Aha Moment

"What if the server never sees your data at all?"

Client (browser)
User
password
Key Derivation
(PBKDF2)
key
Your Data
[AES-256] Encrypt
ciphertext
Server
Stores only
ciphertext
(useless to attacker)
Even if hacked: nothing
[AES-256] encrypt
nothing to read

The 3 Primitives

Primitive What it actually does
PBKDF2 / Argon2 Turns your password into a key. Slow on purpose to stop brute force.
AES-256-GCM Encrypts your data using that key. Fast, authenticated, industry standard.
Key Wrapping Encrypts the key itself for safe storage. Lose this = lose everything.
Order matters: password key encrypt data. No math. Just sequence.

Client-Side Encryption Code

async function encrypt(password, data) {
  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey(
    "raw", enc.encode(password), "PBKDF2", false, ["deriveKey"]);
  const aes = await crypto.subtle.deriveKey(
    { name: "PBKDF2", salt, iterations: 100000, hash: "SHA-256" },
    key, { name: "AES-GCM", length: 256 }, false, ["encrypt"]);
  return crypto.subtle.encrypt(
    { name: "AES-GCM", iv }, aes, enc.encode(data));
}

U2FsdGVkX1+rJ3kTAQIDBAUGBwgJCgsMDQ4PEBESEx RVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTI=

This is what an attacker finds in the database. Useless without the user's password.

What the Server Sees

User typed
Password: MyD0g$Name!
Email:    [email protected]
Card:     4111 1111 1111
Note:     SSH key abc123
Server stores
U2FsdGVkX1+rJ3kT...
AQIDBAUGBwgJCgsM...
DQ4PEBESExRVFhcY...
GRobHB0eHyAhIiMk...
A full server breach exposes nothing readable.

Tradeoffs

Nobody talks about this part.

Forgot your password? Your decryption key is derived from it. No password = no key = no data. Recovery must be designed upfront.
Sharing data between users? You must re-encrypt with the recipient's public key. Every share operation is a cryptographic operation.
Audit logging? You cannot log what you cannot see. You log events and metadata, never content.
"Anyone selling you ZK as simple is selling you something."

Audit Without Seeing

Log the event. Never the content.

BAD ZK is broken

{ email: "[email protected]", password: "abc123", action: "login" }

server sees plaintext one breach = exposure
GOOD ZK preserved

{ userId: "u_8f3a", action: "vault_read", timestamp: 1720000000, ip_hash: "9f3c..." }

fully auditable zero plaintext ZK intact

The One Thing to Take Home

"Encryption at rest is not enough.
If your server can decrypt it, so can an attacker."

Thank you, any questions?