Chatto's Crypto-Shredding Is a Real GDPR Article 17 Implementation — Here's the Architecture Pattern (and Its Limits)
On July 8, 2026, a single-binary, self-hosted team chat app called Chatto went open source and landed on Hacker News' front page with over 1,100 points and 300+ comments — a big number for infrastructure software, and most of the discussion wasn't about the chat features at all. It was about a specific design decision: every user's data is encrypted with a key unique to that user, and when the user deletes their account, the server destroys the key instead of the data. The ciphertext stays on disk, in every backup that was ever taken, forever — but without the key, it's unrecoverable noise. That technique has a name, "crypto-shredding," and it's one of the few genuinely satisfying answers to a question every self-hosted SaaS builder eventually has to solve: what does "delete my data" actually mean once a backup has already copied it somewhere you don't control?
Almost every write-up of Chatto's launch repeated the crypto-shredding claim as a clean privacy win. Chatto's own documentation is more careful than that — and the caveats it states plainly are exactly the details you need if you're planning to build the same pattern into your own product.
What Chatto actually encrypts, in its own words
Chatto's creator described the mechanism directly in the open-source announcement: the app keeps "all personal and chat data fully encrypted at rest with per-user keys that get shredded when a user decides to delete their account." Chatto's self-hosting documentation is more specific about scope: "Chatto encrypts message text and selected durable user-PII fields before writing them to storage," while "attachments, avatars, event metadata, and many other records are outside that field-level envelope." That's an important distinction most coverage of the launch dropped — the encryption boundary in Chatto is a field-level boundary, not a system-level one. Message bodies and specific PII columns get the per-user-key treatment; uploaded files and metadata do not.
The cryptography itself is built on a small open-source primitives library Chatto ships as part of its codebase (pkg/datacrypto), which generates cryptographically random 256-bit keys and performs authenticated encryption using XChaCha20-Poly1305, plus authenticated wrapping and unwrapping of those 256-bit keys. Notably, that library is deliberately minimal — its own README states that "applications retain ownership of associated-data construction, domain separation, key identities, key hierarchies, storage, KMS integration, caching, rotation, and erasure policy." In other words, Chatto didn't buy a black-box encryption product; it built the primitives and then built its own key-hierarchy and deletion logic on top, which is exactly the part worth studying if you want to replicate the pattern.
The GDPR case for doing this
Article 17 of the GDPR gives a data subject the right to obtain erasure of their personal data without undue delay on several grounds, including that the data are "no longer necessary in relation to the purposes for which they were collected," that the subject withdraws consent, or that the processing was unlawful. Nowhere does Article 17 specify a technical mechanism — a controller has to make the data genuinely inaccessible, not merely mark a row as deleted while it's still readable from a backup or a downstream replica. Article 32(1) separately requires "appropriate technical and organisational measures" for security of processing and explicitly names "the pseudonymisation and encryption of personal data" as an example of such a measure. Crypto-shredding sits at the intersection of both: it's an Article 32 encryption control that doubles as an Article 17 erasure mechanism, because destroying the one key that can decrypt a user's data is functionally equivalent to destroying the data itself, without requiring you to physically locate and overwrite every copy that ever existed.
That's the property that makes crypto-shredding attractive for exactly the failure mode most self-hosted software has: nightly database backups, replicated logs, and event stores that keep plaintext copies of user data long after the "live" row has been deleted. If you delete the key instead of chasing every copy, the backup's existence stops being an erasure problem — the ciphertext it contains is inert.
The pattern, in code
You don't need Chatto's exact stack to build this. The pattern is a standard envelope-encryption scheme with one twist: the "envelope" key is scoped to a single user, not to the whole database.
On account creation:
user_key = generate_random_256_bit_key()
store(user_key) in a key store SEPARATE from the encrypted data
# e.g., a dedicated `user_keys` table, a KMS, or a secrets manager —
# anywhere that isn't backed up in lockstep with the data it protects
On writing a protected field (e.g., a message body):
nonce = generate_random_nonce()
ciphertext = XChaCha20_Poly1305_encrypt(plaintext, key=user_key, nonce=nonce)
store(ciphertext, nonce) in the regular data tables
# ciphertext is safe to replicate, log, and back up freely —
# it is meaningless without user_key
On reading a protected field:
user_key = lookup(user_id) in the key store
plaintext = XChaCha20_Poly1305_decrypt(ciphertext, key=user_key, nonce=nonce)
On account deletion (the actual erasure event):
delete(user_key) from the key store — irreversibly, including any
replicas or backups of the key store itself
separately delete non-field-encrypted assets owned by the user
(attachments, avatars, uploaded files) — crypto-shredding does not
cover these, they need their own deletion path
# from this point forward, every ciphertext blob tied to this user,
# in every backup ever taken, is permanently unrecoverable
The part teams most often get wrong isn't the encryption call — AEAD ciphers like XChaCha20-Poly1305 or AES-256-GCM are well-understood and available in every major language's standard crypto library. It's treating the key store as architecturally separate from the data store. If your nightly backup job snapshots the database and the key store together, you've backed up the key next to the ciphertext it unlocks, and deleting the key from production does nothing to the backup copy sitting right next to its own ciphertext. The key store needs its own retention and backup policy, decoupled from the main data backup — ideally with a much shorter retention window, or none at all for deleted users.
The three caveats Chatto's docs admit that most coverage skipped
This is where Chatto's own documentation is more honest than the press cycle around its launch, and it's the part actually worth internalizing before you build this pattern yourself.
-
"Attempts" to shred, not guarantees it. Chatto's security documentation describes account deletion as an operation that "attempts to crypto-shred the protected fields' keys and separately remove user-owned asset bytes" — the word "attempts" is doing real work there. Key deletion across a distributed key store, a replicated database, and any warm standby is an operational problem, not just a cryptographic one; a key that lingers in a replica lag window or a not-yet-expired cache is a key that still works.
-
The application boundary is not a cryptographic boundary. Chatto states this explicitly: "Someone who controls the running server, its storage, and its encryption keys has more access than an in-app administrator." Field-level per-user encryption protects against someone reading the database directly, or against a backup leak — it does not protect against a compromised or malicious server operator who can read keys and plaintext at the same time the application does. If your threat model includes the hosting operator itself, crypto-shredding alone isn't the control that addresses it.
-
Backups can defeat the whole mechanism if you're not deliberate about them. Chatto's own backup tooling ships a flag specifically for this:
chatto backupwithout--include-keysexcludes the key material from the backup archive, and the documentation warns operators to "treat either form as sensitive and align old data and key retention with your account-deletion policy." Read that carefully — it means the default backup behavior still needs an explicit operator decision, and a misconfigured backup job that includes keys alongside data silently defeats crypto-shredding for anyone restoring from that backup later. This is the same category of problem covered in more general terms in our GDPR right-to-erasure guide for backups, logs, and event stores — Chatto's launch is simply a concrete, shipped example of a team building exactly that discipline into their tooling instead of leaving it as a manual runbook step.
None of these caveats make crypto-shredding a bad pattern — they make it a pattern that requires the same operational rigor as any other security control, not a magic compliance checkbox you flip once and forget.
A checklist if you're building this into your own self-hosted app
- Generate one cryptographic key per user (256-bit, from a real CSPRNG), never a key shared across users or derived from a static application secret.
- Store keys in a location with independent backup/replication policy from the encrypted data itself — a shared backup job is the most common way this pattern silently breaks.
- Use an authenticated encryption mode (XChaCha20-Poly1305, AES-256-GCM) — never unauthenticated encryption for anything that needs erasure guarantees.
- Decide explicitly, and document, which fields sit inside the field-level encryption boundary and which don't (attachments, avatars, audit logs, and metadata usually need a separate deletion path).
- On account deletion, delete the key first and treat that as the authoritative erasure event — then separately delete non-encrypted assets, and confirm both steps actually ran.
- Audit your backup tooling specifically: does a routine backup job capture key material alongside data? If yes, that backup needs its own shorter retention and its own deletion trigger tied to account deletion.
- Write down your threat model explicitly: crypto-shredding protects against stolen backups and unauthorized database reads, not against a hosting operator with server and key access at the same time.
See also
For the broader mechanics of GDPR Article 17 erasure across backups, event-sourced systems, and processor chains — including a second erasure technique (backup expiry/isolation) for cases where per-user encryption isn't practical to retrofit — see our GDPR right-to-erasure guide for backups, logs, and event stores.
Primary sources: Chatto is now open source — hmans.dev (creator's own announcement, per-user key and crypto-shredding claim, July 8, 2026) · Chatto Security & Privacy — docs.chatto.run (official docs: encryption boundary, "attempts to crypto-shred" wording, admin/server-operator caveat, chatto backup --include-keys flag) · chattocorp/chatto — GitHub (AGPL-3.0-or-later license, tech stack, links to the datacrypto module) · pkg/datacrypto README — GitHub (XChaCha20-Poly1305 authenticated encryption, 256-bit key generation and wrapping, minimal-library design) · Hacker News discussion via Algolia HN Search (1,106 points, 305 comments, July 8, 2026) · Regulation (EU) 2016/679 (GDPR), Article 17 (right to erasure) and Article 32(1) (security of processing).
EU-Native Hosting
Ready to move to EU-sovereign infrastructure?
sota.io is a German-hosted PaaS — no CLOUD Act exposure, no US jurisdiction, full GDPR compliance by design. Deploy your first app in minutes.