Node Crypto vs Web Crypto

As usual, just when you think you have things sorted, something spoils your fun!

All I wanted to do was to create a signed URL to a Google Cloud Storage bucket. It took a bit of trial and error, but then I solved it. The problem now being that the crypto sign blows my 50ms CPU time.

I had been using NodeJS crypto package, the function to sign was simple …

const sign = (input, key) => crypto.createSign(‘RSA-SHA256’).update(input).sign(key, ‘base64’)

The key being passed is in PEM format private key (a string). Functionally this works, and I can use the result to access my Google bucket. I know that the sign process is consuming the >50ms CPU. With the signing process stubbed out - my worker is consuming 18ms per request.

My first thought was to preprocess the key using crypto.createPrivateKey - caching the result and using this instead of the plain text key. However, crypto.createPrivateKey is not available to the Worker.

Second thought is to use the Worker’s Web Crypto API. This seems to be similar, but I can’t find any examples of usage for an equivalent to what I have coded in Node crypto. I’ve even seen a suggestion that it’s not possible to create it.

Can anyone advise/suggest anything to improve the performance of the signing process. I know that I can implement as a Google Cloud Function - but I’m trying to keep it simple.

Take a peek at this:

This is using HMAC SHA256, but something similar should work for RSA.

More info here:

Thanks David.

I’ve now engaged my brain, and this is the resultant solution to my RSA-SHA256 signing issue …

First, convert and decode the PEM string from base 64 into a Buffer object (I will cache and persist this to the KV store.) …

function convertPem (pem) {
  return Buffer.from(pem.split('\n').map(s => s.trim()).filter(l => l.length && !l.startsWith('---')).join(''), 'base64')
}

Second part, create a CryptoKey (using the object result from the converPem function)

function createSigningKey (keyData) {
  return crypto.subtle.importKey(
    'pkcs8',
    keyData,
    { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
    false,
    ['sign']
  )
}

We can now use the result of the createSigningKey to sign. In my case I have a string of text, I convert it to a Buffer object, sign the content of the buffer using a key derived from createSigningKey, and finally return a base64 encoded string as result.

async function createSignature(text, key) {
  const textBuffer = Buffer.from(text, 'utf8')
  const sign = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, textBuffer)
  return Buffer.from(sign).toString('base64')
}

Bada bing.

In terms of timing, based on observation of network response time, it’s saving over 100ms. I’m back well within my 50ms limit :smile:

1 Like