# Encrypted backups in the browser, done right Source: https://selfstore.dev/blog/encrypted-backups-browser What client-side encryption actually protects against, why AES-256-GCM over Argon2id, and why a format spec beats a vendor promise. "Your data is encrypted" is the least informative sentence in software. TLS in transit? At rest on the vendor's disks, with the vendor holding the keys? The threat models differ so much that the same words describe opposite architectures. This article states one architecture precisely: browser apps that encrypt **before the data leaves the device**, as [selfstore](https://selfstore.dev/) implements it, and what that does and does not protect. ## The claim worth making When a backup file leaves the user's device already encrypted, under a key derived from a password only the user knows, the storage provider holds opaque bytes. Google, the Nextcloud admin, whoever finds the USB stick: they learn the file's size, its creation date and nothing else. There is no server-side key to subpoena, mismanage or leak, because there is no server in the loop at all. That is a different claim from "we encrypt your data", and it is checkable, which is the point of this article's last section. ## The primitives, and why these **AES-256-GCM** for the payload. Authenticated encryption is not optional: a backup that decrypts to silently corrupted state is worse than one that fails. GCM gives confidentiality and integrity in one pass; a wrong password, a flipped ciphertext byte or tampered parameters all fail the same way: loudly, with no partially valid read. **Argon2id** to turn a human password into a key. Passwords have to assume offline brute force, so the derivation must be expensive in the attacker's favorite currency, memory: 46 MiB and 3 passes by default. Two details matter more than the defaults, though: - **Parameters travel with the file.** Each backup records its own KDF cost, so files written under old defaults keep decrypting forever, and defaults can improve without a migration. - **Parameters are bounded on read.** A hostile file claiming 8 GiB of KDF memory is refused before the derivation starts. Reading a backup must never be a denial-of-service on the reader. Two operational details, honestly told: JavaScript cannot zeroize strings, so the password itself lives at the platform's mercy (derived keys are non-extractable WebCrypto keys, which is what the platform can promise). And Argon2id at real cost would freeze a UI thread, so it runs in a dedicated worker, falling back silently to the main thread where workers are not available, with byte-identical output. ## The file is a ZIP on purpose An encrypted selfstore backup is still a valid ZIP archive: a cleartext `meta.json` (app name, dates, KDF parameters, IV), the ciphertext, and a human-readable note. A person who finds the file in 2036 can tell what it is and which app reads it, without any secret. What they cannot do is read the data. The unencrypted variant opens in any archive tool: a JSON manifest plus the user's files. No mystery blobs in either mode: the difference between the two is exactly one property, confidentiality, not inspectability of the container. ## Verifiability beats promises Everything above is an implementation's word. What makes it trustworthy is that the format is [specified independently](https://selfstore.dev/docs/format) of the library: SPEC.md defines the container, the header fields and the crypto envelope; canonical test vectors pin the bytes; and a ~120-line Python reference reader, sharing zero code with the TypeScript, reads real backups. If the library disappeared tomorrow, the files remain readable from the spec alone, and any claim in this article can be checked against an artifact rather than taken on faith. One last honest note, because trust also means stating limits: none of this defends against malicious code running **inside** your app's origin. XSS owns the session, including the data already decrypted in memory and the device key that unseals the local cache; the [threat model](https://github.com/selfstoredev/selfstore/blob/main/THREAT-MODEL.md) spells out those non-goals. Client-side encryption protects the data at rest and in the cloud, and that is precisely the claim, no more, no less. Map of this site for a model: https://selfstore.dev/llms.txt Every page in one file: https://selfstore.dev/llms-full.txt