hometools

Encrypting Static Site Content with WASM

April 2026

Overview

The page encrypts files so they can be password protected on static sites without additional server side processes. It's the counterpart to Password Protecting Static Site Content with WASM. Check out that page for details on the combination.

The Encryptor

Wrap Up

I use this page to encrypt single files. I use a command line version (e.g. from Encrypt a File with a Password in Rust) when I'm working on batches of files.

The source code is below. I was pleasantly surprised by how little it took to get things running. Of course, the Rust library that's doing all the heavy lifting is way more complicated.

Very cool.

-a

The Source Code

Rust Code

src/lib.rs
use orion::{aead, kdf};
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn encrypt_file(
  bytes: Vec<u8>,
  password: String,
) -> Vec<u8> {
  let kdf_password =
    kdf::Password::from_slice(password.trim().as_bytes()).unwrap();
  let salt =
    kdf::Salt::from_slice(b"ca0beb1d-5a8a-4da9-b278-058087d00125")
      .unwrap();
  let kdf_key =
    kdf::derive_key(&kdf_password, &salt, 3, 1 << 8, 32).unwrap();
  aead::seal(&kdf_key, &bytes).unwrap()
}
Cargo.toml
[package]
name = "static-site-file-encryption"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[dependencies]
getrandom = { version = "0.4.2", features = ["wasm_js"] }
orion = "0.17.13"
wasm-bindgen = "0.2.117"

Rust Build Script

built-release.bash
#!/bin/bash

[ -e pkg ] && rm -rf pkg 
wasm-pack build --target web
[ -e "pkg/.gitignore" ] && rm "pkg/.gitignore"

Web Page Code

HTML
<form id="encryptForm">
  <label>
    <div>File To Encrypt:</div>
    <input type="file" name="file" />
  </label>

  <label>
    <div>Password to use:</div>
    <input type="password" name="password" />
  </label>

  <div>
    <input type="submit" value="Encrypt File" />
  </div>
</form>
JavaScript
<script type="module">
  import init, {encrypt_file} from "./pkg/static_site_file_encryption.js";

  async function handleEncryption(event) {
    event.preventDefault();
    const data = new FormData(event.target);
    if (!data.get("file") || !data.get("password")) {
      console.error("Handle missing inputs here");
      return;
    }
    const arrayBuffer = await data.get("file").arrayBuffer();
    const bytes = new Uint8Array(arrayBuffer);
    const responseBytes = await encrypt_file(bytes, data.get("password"));
    const blob = new Blob([responseBytes], { type: "application/octet-stream" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `${data.get("file").name}.bin`;
    a.click();
    URL.revokeObjectURL(url);
  }

  async function main() {
    await init();
    const fileInput = document.querySelector("#encryptForm");
    fileInput.addEventListener("submit", (event) => {
      handleEncryption(event);
    });
  }

  main();
</script>

References