LibreLedger is a single-person money ledger. The browser encrypts
everything (PBKDF2-HMAC-SHA256 with 250k iterations, then AES-256-GCM),
and a small standard-library Python server stores only the resulting
{v, salt, iv, ct} blob. Ledger files from the earlier Money Ledger version
open unchanged.
- On plain http to a LAN or VPN address, where browsers hide Web Crypto,
the app switches to vendored @noble/hashes and @noble/ciphers 2.2.0 and
says so on the lock screen. tests/crypto-interop.test.mjs shows both
paths read each other's files.
- The server hands out only the app's own files, sends a self-only CSP,
nosniff, frame denial and no-referrer, checks the shape of each blob,
refuses cross-site writes and writes atomically. It keeps the last 10
backups plus one per day for 30 days.
- The container runs that server from a digest-pinned python alpine
image, as an unprivileged user, with its data in /data and a health
check on /api/health.
Assisted-by: Claude Opus 5 <noreply@anthropic.com>
4.5 KiB
Vendored crypto
LibreLedger encrypts in the browser. Where the page is a secure context
(HTTPS, http://localhost) it uses the browser's Web Crypto API. Where it is
not (plain http:// to a LAN or VPN address), browsers hide crypto.subtle,
and crypto-fallback.js runs the same PBKDF2-HMAC-SHA256 and AES-256-GCM from
these files instead. They are loaded only in that case, and the server serves
only the files listed in server.py (VENDOR_FILES).
| Package | Version | Released | Licence | npm tarball integrity |
|---|---|---|---|---|
| @noble/hashes | 2.2.0 | 2026-04-11 | MIT | sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg== |
| @noble/ciphers | 2.2.0 | 2026-04-11 | MIT | sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA== |
Both are zero-dependency libraries by Paul Miller. Audit history, from their
READMEs: independent Cure53 audits of @noble/hashes 1.0.0 (Jan 2022) and
@noble/ciphers 1.0.0 (Sep 2024), and a self-audit of exactly 2.2.0 for both
(Apr 2026), which is why 2.2.0 is the version pinned here. The files below are unmodified
copies of the compiled ES modules from those tarballs, with each package's
LICENSE. Only the modules needed for PBKDF2-SHA256 and AES-GCM are kept.
The tarballs were checked against the integrity values above before
extracting.
File hashes
sha256sum:
d079978bb7dac51e88586dd881ea3d7be010624b876d332f395f4a2b934f5fbc noble-ciphers-2.2.0/aes.js
f36671a5487c9c5050efacb58011c37c24c55a889803cb036cf9d9a6347c1e2d noble-ciphers-2.2.0/LICENSE
87a6cdf9cad2cec61229404cc7f8da1214952a76114b8a6342a3e771f1127cd1 noble-ciphers-2.2.0/_polyval.js
cfb9806b3339c79544a48a7026ee3c868cd1f0a1555c5eef4854b7eec43ccb63 noble-ciphers-2.2.0/utils.js
137ed94227806b351a55b09801287a4dba72d2a35d3838730becf641271bc3dd noble-hashes-2.2.0/hmac.js
4f221aee6e072336700c408c68ab3b96a3fc09f6aebe6f48f1bd99e5ef13faec noble-hashes-2.2.0/LICENSE
8227b9b5cabf078a9d7f7317f7a1ace6e46627539aa9364667aec724e1636f14 noble-hashes-2.2.0/_md.js
d61f870b99cf8e67b6df0ae5f582c74177445844d6a92ea51e8060032d05b555 noble-hashes-2.2.0/pbkdf2.js
0fb8e3c3f2c73a890be2524ac5d2542aaed4decff69e561231a86131203b3973 noble-hashes-2.2.0/sha2.js
766b91a693a798f9d3cde97b25db4a6d0cef66b2ca21153d3d42424d37878870 noble-hashes-2.2.0/_u64.js
e2adfc13c846487feff0410bd5508a1d66f5ebadc3188f3a40a6b55449981e2f noble-hashes-2.2.0/utils.js
Subresource-integrity form (sha384):
sha384-0YHayko0KfpRNxkCNkZTuDkZIofvvDbgY4URpQALCIVKUglJYgnGQ4qGfXFFzXe8 noble-ciphers-2.2.0/aes.js
sha384-RCB3oUufLSfqmAfe/Csz1PRWhjGYjVj48y1X4s/XpyPQk1Bcd2Pa54pZCBo+u7O1 noble-ciphers-2.2.0/LICENSE
sha384-IHfhTh3OeAiHoY7cB8F9YUBJ9rn6RieetfuNQZY75cIgd5UcjVqOCh6ssn0gIGed noble-ciphers-2.2.0/_polyval.js
sha384-VZAiMxOScRfdOr1e2+N5h6GRH5I1mBeWGL1BSjmYLB9VuR/ODNHxvqbMzPIvwwXV noble-ciphers-2.2.0/utils.js
sha384-jjTbQ/7iXpfi7CA4vLI8VW2ljCxeMnfWE6FYpgMrpYgpT+IKKDJLplkP3KlaqLXp noble-hashes-2.2.0/hmac.js
sha384-a19tauEwIs1iP+2eFZxRsChHXNFBtRprDPX8zuYyn/tuG+DeZzAzH1Fv/gHlGrk7 noble-hashes-2.2.0/LICENSE
sha384-cQrKviUiqTLrOSU3x5KyKb6vIptltNRJQbJGyyXy0+QCUUpOHRwOLM0JuIE//YJB noble-hashes-2.2.0/_md.js
sha384-z3+NFrFNV0hs4V5jqy3EAGPvJQAi902QeBkbt5se8GRLG02Vbdz12785sBD3H/mx noble-hashes-2.2.0/pbkdf2.js
sha384-lVYA2l9kQw+NU1uh5QaTLMkTJaFpmFJuIohWWtXdZ5Ri+AdJ+gG7dWCnv0VpvBYt noble-hashes-2.2.0/sha2.js
sha384-j/S9oLRxzt1W++wUX4Lid0ZXyDVraPlN3I5Olt/q+FBuApzg+Tk6GAjTioMmzu0G noble-hashes-2.2.0/_u64.js
sha384-eVOwgoKAAt0Ur0m4yWkKHg9hlMn4HGGrUW0PlHqEzo7hYCelwnnIejIRYCoUIbbf noble-hashes-2.2.0/utils.js
Checking them yourself
for p in hashes ciphers; do
curl -sSfO "https://registry.npmjs.org/@noble/$p/-/$p-2.2.0.tgz"
echo "$p sha512-$(openssl dgst -sha512 -binary $p-2.2.0.tgz | base64 -w0)" # compare with the table
mkdir -p "$p" && tar xzf "$p-2.2.0.tgz" -C "$p"
done
for f in noble-hashes-2.2.0/*; do cmp "$f" "hashes/package/${f#*/}"; done
for f in noble-ciphers-2.2.0/*; do cmp "$f" "ciphers/package/${f#*/}"; done
Updating
Pick a release that has been out for a while, verify its tarball integrity
against the npm registry, copy the same files into a new versioned folder,
update the imports in crypto-fallback.js, VENDOR_FILES in server.py and
this file, then run tests/crypto-interop.test.mjs. The modules still carry
their //# sourceMappingURL comments; the maps are not shipped, so a browser's
developer tools may note a missing map, which is harmless.