Skip to content

LockHTML v2.1.0

Choose a tag to compare

@drsamonline drsamonline released this 21 Oct 02:05
· 28 commits to main since this release

LockHTML v2.1.0

age interoperability — LockHTML now reads and writes standard age files.

The item I deferred in v2.0 is done, and done the way it should have been: verified against the reference implementation rather than against my reading of the spec.

What you can do now

lockhtml keygen --type age -o key.txt        # an age identity; prints the recipient
lockhtml age-encrypt report.pdf -r age1abc…  # -> report.pdf.age
lockhtml age-decrypt report.pdf.age -i key.txt -o report.pdf

The key file is in the standard age format, so age -i key.txt works on it too — and files either tool produces, the other can open.

Supported

  • age v1 header and its MAC
  • X25519 recipients and identities (Bech32 age1… / AGE-SECRET-KEY-1…)
  • the 64 KiB ChaCha20-Poly1305 STREAM payload
  • multiple recipients per file
  • unknown stanza types are skipped on decrypt, as the spec requires

Not implemented: scrypt passphrases, the ML-KEM / X-Wing hybrid and P-256 tagged recipients, and ASCII armor.

Why it is CLI-only

age uses ChaCha20-Poly1305, which the browser's Web Crypto API does not expose — only AES-GCM. So the HTML viewer and the GUI cannot open age files. This is stated plainly in the docs rather than left as a surprise.

How it was verified

  1. The official vector. testdata/example.age and its identity from FiloSottile/age are embedded in the test suite, so CI decrypts a real reference-produced age file with no network access.
  2. Bidirectional interop against the age v1.2.1 binary — empty input, 1 byte, 64 KiB−1, exactly 64 KiB, exactly 128 KiB, 64 KiB+1, multi-chunk, and binary data — both directions, plus multi-recipient files.

That exercise caught a real bug. A payload whose length is an exact multiple of 64 KiB decrypted incorrectly: the final chunk is full-sized, so a naive reader treats it as non-final and the tag check fails. The spec sentence that matters ("the final chunk MAY be shorter than 64 KiB") does not make that case obvious — only the reference binary surfaced it. Fixed, and covered by tests at 65536 and 131072 bytes.

Honest caveat

This is a from-scratch implementation of a published format and it has not had an independent security review. It interoperates with the reference implementation on the cases above, and it refuses low-order X25519 shares and length-checks stanza bodies as the spec requires — but for anything that matters, prefer the reference age tool. That caveat is in SECURITY.md and docs/AGE.md, not just here.

Verification

111 unit tests (24 of them new for age), 35/35 headless-Chrome checks, 9/9 browser→Python cross-implementation checks.

Live GUI

👉 https://drsamonline.github.io/lockhtml/