CryptoKey bindings in Cloudflare Workers - importKey at publish time!

CryptoKey bindings in Cloudflare Workers - importKey at publish time!
Photo by Kaffeebart / Unsplash

If you use the Web Crypto API, chances are you're using  importKey - this is an async method that takes the format, usages, algorithm & the key and gives you a CryptoKey object.

If your key is never changing (or very infrequently), having to import on every request is wasteful - both for CPU & wall-time. You can somewhat mitigate this by throwing it in the global scope & Worker executions on the same isolate will be able to access it, but Workers can be evicted at any time and this isn't a reliable method.

When the Cloudflare Workers runtime went open-source, it didn't take long for people to find some bindings that aren't documented - in particular, CryptoKey. This allows you to do the importKey at publish time and the resulting CryptoKey object will be available to the Worker like any other binding.

How do we use it? Well, it's not officially in Wrangler yet - but Wrangler has an 'unsafe binding' functionality where you can push any object you like onto the bindings payload which is often used for early betas that haven't decided on a stable binding.

Let's take a look at how they're used in workerd...

(
    name = "hmacBase64",
    cryptoKey = (
        base64 = "dGVzdGtleQ==",
        algorithm = (
            json = `{"name": "HMAC", "hash": "SHA-256"}
        ),
        usages = [ sign ]
    )
),
https://github.com/cloudflare/workerd/blob/main/src/workerd/server/server-test.c%2B%2B#L718

How does this translate into our wrangler.toml? Let's take a look:

[[unsafe.bindings]]
type = "secret_key"
name = "my_key"
format = "raw"
algorithm = { name = "HMAC", hash = "SHA-256" }
usages = ["sign"]
key_base64 = "key_data_here"

How can we leverage it in a Worker? Just reference the CryptoKey available under env.my_key:

interface Env {
	my_key: CryptoKey
}

export default <ExportedHandler<Env>> {
	fetch(req, env, ctx) {
		console.log(env.my_key.usages)

		return new Response('')
	}
}

Here's an example of how to use the CryptoKey object:

interface Env {
	my_key: CryptoKey
}

export default <ExportedHandler<Env>> {
	async fetch(req, env, ctx) {
		const algo = { name: "HMAC", hash: "SHA-256" }
        
		const enc = new TextEncoder();
		const data = enc.encode("lol")

		const signed = await crypto.subtle.sign(algo, env.my_key, data)

		const verified = await crypto.subtle.verify(algo, env.my_key, signed, data)
		console.log(verified) // true
		
		return new Response('')
	}
}

Take a look at the workerd source code to see the different key types you can use.