Skip to content

Repository files navigation

 ██████╗███████╗██╗     ███████╗██████╗ ███████╗ ██████╗██████╗ ██╗   ██╗██████╗ ████████╗
██╔════╝██╔════╝██║     ██╔════╝██╔══██╗██╔════╝██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗╚══██╔══╝
╚█████╗ █████╗  ██║     █████╗  ██║  ██║█████╗  ██║     ██████╔╝ ╚████╔╝ ██████╔╝   ██║
 ╚═══██╗██╔══╝  ██║     ██╔══╝  ██║  ██║██╔══╝  ██║     ██╔══██╗  ╚██╔╝  ██╔═══╝    ██║
██████╔╝███████╗███████╗██║     ██████╔╝███████╗╚██████╗██║  ██║   ██║   ██║        ██║
╚═════╝ ╚══════╝╚══════╝╚═╝     ╚═════╝ ╚══════╝ ╚═════╝╚═╝  ╚═╝   ╚═╝   ╚═╝        ╚═╝

📄 anything  ──▶  🔒 encrypt  ──▶  📦  SelfDecrypt.html  ──▶  🔓 password  ──▶  📄 anything

              everything runs in the browser · zero servers · zero network

SelfDecrypt

Turn files and text notes into a password-protected, self-decrypting HTML file. Add one or more files, an optional text note, or both, enter a password, get a single SelfDecrypt.html that carries both the ciphertext and the decryption app. Email it, Slack it, put it on a USB stick. The recipient opens it in any browser, types the password, and the original contents fall back out. No app to install, no account, no server, no upload.

Try it, or grab the latest single-file build and use it offline.

Good for: sharing passwords with non-technical people, digital-inheritance and emergency instructions, 2FA recovery codes, or password-protecting sensitive documents without installing PGP, 7-Zip, or anything else. It also makes a great travel backup: encrypt copies of your passport, documents, and key phone numbers, host the file anywhere public, and if you lose every device you can still get to them from any borrowed browser with nothing but your password.

How it works

  • Fully client-side: encryption happens entirely in your browser. The generated SelfDecrypt.html embeds the ciphertext inline and makes zero network requests, decryption also runs entirely in the recipient's browser. Every generated file also carries a Content-Security-Policy whose default-src is 'none', so the browser enforces this rather than taking the code's word for it.
  • Self-cloning: there is only one app. On load it looks for an embedded <script type="application/json" id="payload"> holding { version, ciphertext }. If it finds one it shows the decrypt UI, otherwise it shows the encrypt UI. Encrypting serializes the page's own HTML (payload stripped first) and inserts a fresh payload block before </body>, so every generated file is a full copy of the tool. File names and the text note live inside the encrypted container, so the page source leaks no metadata about the contents.
  • Mode toggle and empty copy: because each generated file is the whole app, a payload-bearing clone can switch to encrypt mode ("Encrypt a file instead") and back, and the encrypt UI offers a "Save an empty copy of this tool" link that saves a payload-free SelfDecrypt-empty.html for offline self-distribution.
  • Format: age passphrase encryption, via the age-encryption library (typage). age is a simple, modern encryption format with no config knobs and no legacy footguns. Its TypeScript implementation is written and maintained by Filippo Valsorda, the cryptographer who maintains the Go standard library's cryptography and led Google's Go Security team. It depends only on the widely-used, audited noble cryptography libraries and uses the browser's native Web Crypto API where available, so nothing is hand-rolled.
  • Key derivation: scrypt derives the encryption key from your password, deliberately slow to resist brute-force guessing. The encrypt form rates your password as you type.
  • Cipher: ChaCha20-Poly1305 AEAD provides authenticated encryption, so tampered ciphertext is rejected rather than silently decrypted.

Usage

  1. Open the app: https://johnnyperkins.github.io/SelfDecrypt/
  2. Add one or more files, write an optional text note, and enter a password.
  3. Download the generated SelfDecrypt.html.
  4. Send that file to your recipient, and share the password through a separate channel.
  5. The recipient opens SelfDecrypt.html in any browser, enters the password, and gets the note and files back. A vault holding a single file downloads it immediately.

Prefer the command line? The embedded ciphertext is a standard age file: pull the base64 payload out of the HTML and feed it to age --decrypt. The decrypted output is a small length-prefixed container holding the files and note.

FAQ

Is it safe to send the HTML file over email or chat? Yes, that's the point. Without the password, an attacker faces scrypt key derivation and authenticated ChaCha20-Poly1305. Just share the password through a different channel than the file.

Do pick a real password, though. Anyone who receives the file can guess against it offline for as long as they like, so the password is the whole of your security once it leaves you - scrypt only makes each guess expensive. A few unrelated words beat a short clever one.

Does it work offline? Completely, in either direction. You do not have to take that on faith: open the file in a text editor and read the Content-Security-Policy in its <head>. default-src 'none' means the browser refuses every request the page could make, whatever the script does.

Is there a file size limit? JavaScript engines cap strings around 512 MB and the ciphertext is base64-encoded into one HTML document, so files up to a few hundred megabytes work. Your movie collection does not.

Why age instead of AES-GCM / PBKDF2 like similar tools? age is a scrutinized, standardized format maintained by professional cryptographers, its scrypt KDF is memory-hard (stronger against GPU cracking than PBKDF2), and your files stay recoverable with the independent age CLI forever.

Development

npm install
npm run dev        # start the authoring app with HMR
npm test           # vitest suite (watch)
npm run test:run   # vitest suite, single pass
npm run test:e2e   # playwright e2e suite, chromium/firefox/webkit
npm run typecheck  # tsc across src, e2e, and the build config
npm run lint:ci    # biome lint + format check, as CI runs it
npm run build      # produce the single-file dist/index.html

The e2e suite needs browsers: npx playwright install --with-deps chromium firefox webkit.

Under npm run dev the page is served with external <script src> tags, so it cannot inline itself; a dev-only /__self.html middleware runs a real production build in-process and the encrypt flow clones that instead. It uses the same plugins and build options as npm run build, so what you get in dev is byte-for-byte what ships.

Releases

Packages

Used by

Contributors

Languages