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?
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?
Share Improve this question asked Feb 18, 2019 at 7:48 TachyonTachyon 2,4113 gold badges24 silver badges49 bronze badges2 Answers
Reset to default 13I'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.
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:
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).
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.
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
I have a public key in pem format encoded in base64, Here is how i used it to load as a public key.
export async function importPublicKey(key) {
// Decode the base64-encoded PEM string
let pem = atob(key);
// Define the PEM header and footer for a public key
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
// Extract the base64-encoded content from the PEM string,
// removing the header and footer. The '-1' in the substring function
// accounts for the newline character at the end of the header.
const pemContents = pem.substring(
pemHeader.length,
pem.length - pemFooter.length - 1
);
// Decode the extracted base64-encoded content to get the binary data
const binaryStr = window.atob(pemContents);
// Convert the binary string to an ArrayBuffer
const buff = str2ab(binaryStr);
// Function to convert a binary string to an ArrayBuffer
function str2ab(str) {
const buffer = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
buffer[i] = str.charCodeAt(i);
}
return buffer;
}
// Import the public key using the Web Crypto API
return window.crypto.subtle.importKey(
"spki", // The format of the key to be imported (SubjectPublicKeyInfo)
buff, // The public key data
{
name: "RSA-OAEP", // The algorithm the imported key will be used with
hash: "SHA-256", // The hash function to be used with the algorithm
},
true, // Whether the key is extractable
["encrypt"] // The intended use for the key (encryption in this case)
);
}
and to encrypt data using the public key:
export async function encryptWithPublicKey(data, publicKey) {
const encodedData = new TextEncoder().encode(data);
const encryptedData = await window.crypto.subtle.encrypt(
{
name: "RSA-OAEP",
},
publicKey,
encodedData
);
return window.btoa(String.fromCharCode(...new Uint8Array(encryptedData)));
}