"The server was hacked. Nothing was exposed."
OWASP (The Open Web Application Security Project) France chapter at Theodo offices, May 18th
| Company | Year | What the attacker got |
|---|---|---|
| 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 |
"The server encrypts before storing. We're safe."
| 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. |
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.
Password: MyD0g$Name! Email: [email protected] Card: 4111 1111 1111 Note: SSH key abc123
U2FsdGVkX1+rJ3kT... AQIDBAUGBwgJCgsM... DQ4PEBESExRVFhcY... GRobHB0eHyAhIiMk...
Nobody talks about this part.
{ email: "[email protected]", password: "abc123", action: "login" }
server sees plaintext one breach = exposure{ userId: "u_8f3a", action: "vault_read", timestamp: 1720000000, ip_hash: "9f3c..." }
fully auditable zero plaintext ZK intactThe 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?