Crypto in the Browser
No, don’t worry, I’m not writing about
that kind of crypto.
Instead, I’m referring to the
SubtleCrypto
API found in modern browsers. Oh, and technically not in a browser, since I’m
writing about Cloudflare Workers, but eh,
close enough.
I recently found myself needing to verify the authenticity of incoming webhook
payloads in a Cloudflare Worker. Realizing the worker runtime is “browser-like”,
and therefore implements the SubtleCrypto
interface, I finally had a reason to
try it out.
Importing the Key
The service sending the webhooks signed their payload with a shared secret, which was generated roughly like this:
$ ruby -r securerandom -e "puts SecureRandom.alphanumeric(64)"
A4S7Fd0ZPbOTVkYtjxyIbrHT60lDszkdw3mCkNEPuahwYQGDKCm5Cd72QmMxGrWF
Before verifying anything, the secret needs to be imported as a key. In my case,
the signing algorithm was HMAC-SHA256
, for which a key can be imported like
this:
const keyData = new TextEncoder().encode(sharedSecret)
const key = crypto.subtle.importKey(
"raw",
keyData,
{
name: "HMAC",
hash: "SHA-256",
},
true,
["verify"]
)
importKey()
returns a CryptoKey
, which can be used for whatever operations
were specified as the last argument, but for my purposes, only needs to be
verify
.
Verification
With key in hand, we can use it for verification purposes via the appropriately
named
verify()
method. It expects the to-be-verified data as an ArrayBuffer
, so the
arrayBuffer()
method on Request
is very helpful here:
const signature = request.headers.get("X-Signature")
const body = await request.arrayBuffer()
const isValid = await crypto.subtle.verify("HMAC", key, signature, body)
From there, you can return 401 Unauthorized
if the signatures don’t match, or
whatever other action you’d like to take.
Other Possibilities
I think it’s pretty cool you can do such advanced cryptographic operations in the browser with fairly straightforward API’s. It makes me wonder what other use-cases they unlock.
One example I came across was the Portable Secret project. It uses these same API’s to output a self-contained HTML file that can self-decrypt it’s contents when given the correct password. That’s pretty neat.