hometools

Encrypting Static Site Content with WASM

April 2026

Overview

This is a counterpart to /rust/wasm/static-site-file-decryption/. It works fine, but it's a manual process. It exists mainly to support the decryption example. Generally speaking I'll use an offline process during site generation to encrypt files. For example: /rust/general/encrypt-a-file-with-a-password/

Output

Goal: Provide a way to encrypt and decrypt files so they can be served on static sites

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"

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 << 16, 32).unwrap();
  aead::seal(&kdf_key, &bytes).unwrap()
}

HTML

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

  <label>
    <div>Password to use:</div>
    <input type="password" id="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 password = document.querySelector("#password").value;
    const fileInput = document.querySelector("#fileToEncrypt");
    const file = fileInput.files[0];
    if (!file || !password) {
      console.error("Handle missing inputs here");
      return;
    }
    const arrayBuffer = await file.arrayBuffer();
    const bytes = new Uint8Array(arrayBuffer);
    const responseBytes = await encrypt_file(bytes, 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 = `${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>

Build Script

#!/bin/bash

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

https://docs.rs/orion/latest/orion/

https://docs.rs/orion/latest/orion/aead/index.html

https://docs.rs/orion/latest/orion/kdf/index.html

https://docs.rs/orion/latest/orion/kdf/struct.Salt.html

https://docs.rs/wasm-bindgen/latest/wasm_bindgen/

https://doc.libsodium.org/password_hashing/default_phf#guidelines-for-choosing-the-parameters

https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html