1Why We Built This
More and more people are using AI to create documents. Ask it to "output this as HTML," and you get richly formatted documents with layouts, flow diagrams, interactive elements — things PDF and PowerPoint simply can't do. All in a fraction of the time.
But the moment you try to send one of those rich HTML documents externally, you run into a problem.
HTML has no password protection.
Open it in a text editor and the entire content is readable. Send it via email and everything is exposed. PDF has it. PowerPoint has it. ZIP has it. But HTML — nothing.
So we decided to build one.
2What We Built — Secure HTML Container
Smarter than ZIP, freer than PDF. Maybe.
Secure HTML Container — a format that turns any file into a password-protected HTML. Drop a file, set a password. That's it — you get an encrypted HTML file. No server required. Everything runs in the browser.
ブラウザだけで「ファイルをパスワード付きHTMLに変換できる」ツールを公開しました☺
— 謎みくす (@nanomix_ltd) February 27, 2026
処理はすべてブラウザ内で完結。
データは外部に送信されません。
(記事は近々🫡)https://t.co/7mdnCDIDEn#SecureHTMLContainer #WebCrypto #HTML資料 #ClaudeCode
When you open an encrypted file in a browser, this is what you see:
Do not open files from unknown senders.
Enter the correct password and a confirmation screen appears. It shows the file name, size, and — if the author set them — their name and a message. Here you can verify whether the file is really from the person you expect, then choose to "Open in browser" or "Download."
Videos, images, audio, PDFs, and HTML can be viewed or played directly in the browser. Formats that browsers can't open natively (like Excel) are downloaded instead.
Before → After
Anyone can read the contents
Password required
7 Key Features
- Works with any file type — HTML, PDF, images, video, Excel — any file format can be converted to an encrypted HTML. Distributable as an email attachment
- Fully browser-based — Both encryption and decryption use only the browser's
Web Crypto API. No Node.js, no server required - Authenticated encryption — AES-GCM automatically detects any tampering with the encrypted data during decryption
- Works offline — Both encryption and decryption require zero network communication. Your data never leaves the browser
- Embedded author info — Author name and message can be included inside the encrypted data. Recipients can verify who sent the file after decryption, providing anti-phishing protection
- Theme color — Customize the accent color of the lock screen. Use your brand color so recipients instantly recognize it as "the usual color"
- Inline viewer — Videos, images, audio, PDFs, and HTML can be played or displayed directly in the browser. Preview contents before downloading
3How It Works
The encryption tool itself is an HTML file. Open it in a browser, drop a file, set a password. That's all.
The dropped file is read as an
ArrayBufferFile name, MIME type, size, and author info are serialized to JSON and concatenated with the file body
salt + iv + ciphertext + authTag → Base64-encoded
* v1.1 added automatic compression for text-based files. See Crypto Parameters for supported formats and details
Lock screen + confirmation screen + encrypted data + decryption script are assembled into a single HTML. Theme color is embedded directly as a CSS variable
Structure of the Output HTML
Even if you view the source, all you'll see is the lock screen HTML and a meaningless Base64 string. The original file is locked inside the cipher.
4Crypto Parameters
| Parameter | Value | Rationale |
|---|---|---|
| Encryption algorithm | AES-256-GCM | Authenticated encryption. Encrypts and detects tampering simultaneously |
| Key derivation | PBKDF2-SHA256 | Converts password → encryption key. Computational cost deters brute force |
| Iterations | 600,000 | Within the OWASP-recommended range |
| Salt | 16 bytes | Random each time. Same password produces a different key every time |
| IV | 12 bytes | Standard GCM IV length (NIST SP 800-38D recommendation) |
| Auth tag | 16 bytes | For tamper detection. Appended to the ciphertext |
Key Derivation Flow
password (user input)
↓
PBKDF2(password, salt, 600000, 'SHA-256')
↓
AES key (256 bits = 32 bytes)
Data Format
The encrypted data is a .-delimited string. Three base parts + an optional fourth:
base64(salt) . base64(iv) . base64(ciphertext + authTag) [. base64(preview_json)]
The fourth part, preview_json, is plaintext JSON containing file name, size, author info, and theme color. It's used to display file info before the password is entered.
Automatic Text Compression (v1.1)
Because encrypted data is embedded as Base64 in HTML, it's about 33% larger than the original file. v1.1 introduced automatic compression for text-based files before encryption to reduce this bloat.
| Condition | Value |
|---|---|
| Target MIME types | text/*, application/json, application/xml, image/svg+xml |
| Extension fallback | .html .css .js .json .xml .svg .csv .txt .md .yml .php .sh .py .rb .java .c .cpp .ts .tsx .jsx .sql .log etc. |
| Minimum size | 10 KB or larger |
| Algorithm | Deflate (CompressionStream API) |
| Compression ratio | 60—80% reduction for text-based files |
Whether compression was applied is tracked by the compressed flag in preview_json. The decryption side checks this flag and decompresses using DecompressionStream. Binary files (PDFs, images, PPTX, etc.) are not compression targets and behave the same as before.
5Browser-Side Decryption
Decryption runs entirely on the browser's Web Crypto API. No external libraries are loaded at all.
<script type="application/octet-stream"> element. → separate into salt / iv / data (+ preview_json)crypto.subtle.importKeycrypto.subtle.deriveKey for PBKDF2 → AES-256 keycrypto.subtle.decrypt + authTag verificationWrong Password
AES-GCM always fails auth tag verification when the key is wrong, throwing an exception. There's no risk of "partially decrypted corrupted data" being displayed. It's all or nothing.
Brute Force Protection
- 600,000 PBKDF2 iterations — Each password attempt takes hundreds of milliseconds. Mass attempts become impractical
- Attempt limit — Maximum 10 tries. After that, the form locks
- Exponential backoff — Starting from the 3rd failure: 2s → 4s → 8s → ... → max 30s
6Usage and FAQ
The tool itself is a single HTML file. Just open it in a browser.
Double-click the HTML file. No installation needed
HTML, PDF, images, video, Excel — supports files up to about 50 MB
You can set author name, message, theme color, and preview display. Encryption works without these settings too
The button transforms into a progress bar, then switches to a download button when complete. Tap it to save the file
Why no external libraries?
Using something like CryptoJS would make implementation easier. But if the CDN goes down, decryption becomes impossible. The browser's Web Crypto API uses the OS/browser's native crypto implementation, making it far more reliable.
Why PBKDF2 instead of Argon2?
Argon2id is a memory-hard function and a superior key derivation function compared to PBKDF2. But Web Crypto API doesn't support Argon2. To implement it in the browser without external libraries, PBKDF2 is the only option. With 600,000 iterations, it provides sufficient brute-force resistance.
Does encrypting with the same password twice produce the same ciphertext?
No. Salt and IV are randomly generated each time, so even the same password and file produce different ciphertext every time.
Doesn't the file size increase?
Base64 embedding adds about 33%. However, v1.1 introduced automatic compression for text-based files. For HTML documents and source code, compression reduces size by 60—80%, so even after Base64 overhead, the result can be smaller than the original. For images, PDFs, and other internally compressed formats, compression is skipped and the 33% increase applies as before.
7Closing Thoughts
Everything we needed was already inside the browser.
Web Crypto API, AES-256-GCM, PBKDF2 — all standard technologies built into every modern browser. No server, no external libraries. The entire tool fits in a single HTML file.
Now that more people are creating HTML documents with AI, "I want to send this with a password" is a natural need. Secure HTML Container is the format for that.
We wanted to password-protect HTML. There was no way to do it. So we built one. That's the whole story.🥴