node.jsencryptionelectronwebcrypto-api

Why does crypto throw 'ERR_CRYPTO_UNKNOWN_CIPHER' when using AES-KW as encryption algorithm in Electron main process?


I've been developing an app with TIDAL's auth web sdk. In it, they call SubtleCrypto's wrapKey function using parameters nearly identical to those in the example in the MDN docs.

const wrapCryptoKey = async ({
  keyToWrap,
  password,
  salt
}) => {
  const keyMaterial = await getKeyMaterial(password);
  const wrappingKey = await getWrappingKey(keyMaterial, salt);
  return globalThis.crypto.subtle.wrapKey(
    "raw",
    keyToWrap,
    wrappingKey,
    "AES-KW"
  );
};

getWrappingKey:

const getWrappingKey = (keyMaterial, salt) => {
  return globalThis.crypto.subtle.deriveKey(
    {
      hash: "SHA-256",
      iterations: 1e5,
      name: "PBKDF2",
      salt
    },
    keyMaterial,
    { length: 256, name: "AES-KW" },
    true,
    ["wrapKey", "unwrapKey"]
  );
};

However, when this runs, it throws an error that the cipher is unknown. AES-KW is listed as supported for my node version (v20.14.0). Where am I going wrong?

    at SubtleCrypto.wrapKey (node:internal/crypto/webcrypto:715:10) {
  [cause]: Error: Unknown cipher
      at node:internal/crypto/aes:145:27
      at node:internal/crypto/util:430:19
      at new Promise (<anonymous>)
      at jobPromise (node:internal/crypto/util:428:10)
      at asyncAesKwCipher (node:internal/crypto/aes:145:10)
      at Object.aesCipher (node:internal/crypto/aes:212:27)
      at cipherOrWrap (node:internal/crypto/webcrypto:918:12)
      at SubtleCrypto.wrapKey (node:internal/crypto/webcrypto:715:10)
      at async handleNewCryptoKey (../node_modules/@tidal-music/auth/dist/index.js:273:22)
      at async Module.init (../node_modules/@tidal-music/auth/dist/index.js:479:32) {
    code: 'ERR_CRYPTO_UNKNOWN_CIPHER'
  }
}

I've tried updating to the latest node version and restarting & rebuilding my project to no avail.


Solution

  • This problem was caused by calling wrapKey in the main electron process. Electron uses BoringSSL (as opposed to Node.js using OpenSSL), which does not support AES-KW as an algorithm. From Electron issue #41720:

    Node.js proper uses OpenSSL to underpin crypto, whereas Electron uses BoringSSL: a fork of OpenSSL from Chromium pared down significantly compared to Node.js. The actual error here is that the cipher is unsupported, which is a choice made intentionally by BoringSSL. It's implemented in WebCrypto, however, so you're able to use it in the renderer process if you so choose.

    As mentioned in that quote, Electron does use Node.js to back crypto in the renderer process. I solved my problem by creating a temporary browser window and moving my calls to TIDAL's api into it and communicating with the main process via ipcRenderer.