javascriptionic-framework

SubtleCrypto importKey from PEM


I am trying to use the SubtleCrypto Web API in Ionic to encrypt data using a public key. I am importing the key in PEM format and then passing it onto window.crypto.subtle.importKey and then using that result into window.crypto.subtle.encrypt

It would appear that there is a problem with the window.crypto.subtle.importKey - I am getting a Uncaught (in promise): DataError when I am trying to import the key.

I am currently using the following methods to import the key:

//Get the public key in CryptoKey format
let importedPublicKey = await window.crypto.subtle.importKey(
    "pkcs8",
    this.pemPublicToArrayBuffer(serverPublicKey),
    {
        name: "RSA-OAEP",
        hash: {name: "SHA-256"}
    },
    true,
    []
);

private pemPublicToArrayBuffer(pem) {
  var b64Lines = this.removeLines(pem);
  var b64Prefix = b64Lines.replace('-----BEGIN PUBLIC KEY-----', '');
  var b64Final = b64Prefix.replace('-----END PUBLIC KEY-----', '');

  return this.base64ToArrayBuffer(b64Final);
}

private base64ToArrayBuffer(b64) {
  var byteString = window.atob(b64);
  var byteArray = new Uint8Array(byteString.length);
  for (var i = 0; i < byteString.length; i++) {
    byteArray[i] = byteString.charCodeAt(i);
  }

  return byteArray;
}

Does anyone possibly know why the key import is failing with the PEM public key?


Solution

  • I've spent quite some time struggling with this error myself, and now I'm sure I can give you (and anyone else) some good advice on this.

    1. You are passing "pkcs8" as a format to the importKey method, but if you are importing a PUBLIC key, the format is likely to be the "spki" (SubjectPublicKeyInfo) a special format for public keys, while "pkcs8" supposed to be used for PRIVATE keys. That brings us to the next point:

    2. Where did you get this key from? If you are exporting public key with the OpenSSL cli (openssl rsa -pubout -in priv.pem -out pub.pem) then you are getting the key in "spki" format (default one).

    3. You should pass ["encrypt"] as a "usages" parameter to the importKey (instead of an empty array) if you are importing a PUBLIC key, otherwise you will end up with one of the following errors: "SyntaxError: Cannot create a key using the specified key usages" (wrong usage specified for the key) or "InvalidAccessError: key.usages does not permit this operation" (empty array of usages). Things to keep in mind here is that Public keys can be used only to ["encrypt"] and Private keys to ["decrypt"]. I haven't tried to import key pairs though, but as I understand it, you should pass "pkcs8" as a format, and ["encrypt", "decrypt"] for usages.

    4. Even if you set up all the above properly, you may still get the nasty "Uncaught (in promise): DataError", for me it was due to the format mismatch, I've been passing a key in PKCS#1 RSAPublicKey format with the "spki" parameter. So you should probably start from inspecting your key to get exact format and algorithm details from it.

    Hope this helps someone. Ivan