Signing JSON in the browser with Web Crypto
A practical, dependency-free guide to signing a JSON document entirely in the browser — so the private key never leaves the device.
Why sign in the browser?
For a local-first tool, the private key should never touch a server. Web Crypto lets you generate keys, sign documents and verify signatures entirely in the browser — no backend, no key upload, no third-party dependency.
The pieces you need
- Key generation: ECDSA P-256 via
crypto.subtle.generateKey. - Canonicalization: RFC 8785 (JCS) so the exact bytes you hash match what a verifier hashes.
- Digest: SHA-256 of the canonical UTF-8 payload.
- Signature: ECDSA-SHA256, base64url encoded.
- Identity: RFC 7638 JWK thumbprint → RFC 9278 thumbprint URI.
- Key export: encrypt the private JWK with PBKDF2 + AES-256-GCM before download.
The canonicalization trap
JSON object key order is not guaranteed. If you hash the raw JSON, a verifier that re-serializes differently gets a different digest. RFC 8785 canonicalization fixes this — it produces a deterministic byte representation so both sides hash the same thing.
Never sign secrets
A signed document is portable and tamper-evident, but it's not a secret store. Keep passwords, API keys, tokens and private keys out of the signed payload.
See it working
SovereignRoot is a working implementation of exactly this — a local-first generator and verifier built on Web Crypto. Try it, or read the reference code.