I'm trying to exchange public keys between Browser and Server, and generate secret to be used for encryption of data. I'm trying to utilize ECDH (Elliptic Curve Diffie-Hellman)
.
On the Server side I'm generating ECDH
with prime256v1
algorithm.
On the Browser side I'm generating ECDH
with P-256
named curve. (these algorithms should be the same, they are just named differently, P-256
, also known as secp256r1
and prime256v1
).
I'm able to pass Browser generated public key to the server as Base64 formatted string, and to generate secret using Server private key and Browser public key. And everything works fine on the Server side (import, generate secret, encryption).
But when I try to pass Server generated public key to the Browser as Base64 formatted string and try to import it, I get DOMException: Cannot create a key using the specified key usages.
const b64ToBin = (b64) => {
const binaryString = window.atob(b64);
const length = binaryString.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
};
const importB64Key = async (base64key) => {
const bin = b64ToBin(base64key);
console.log('bin ', bin);
const key = await window.crypto.subtle.importKey(
'raw',
bin,
{
name: 'ECDH',
namedCurve: 'P-256',
},
true,
['deriveKey']
);
return key;
};
The keyUsages
value is wrong (as also pointed out by the error message): When importing a public key in the context of ECDH, an empty array []
is to be passed for keyUsages
(deriveKey
and/or deriveBits
are only passed when importing a private key).
Also, keep in mind that the public EC key must be passed as an uncompressed or compressed key when using the format raw
.
If the keyUsages
value is fixed and an uncompressed or compressed key is applied, the posted code works.
(async () => {
var uncomressedKeyB64 = 'BAmL07vrRR5lfkWuH1RAFJufx0B4J+BdOqIYZCH+fJc8c+5sFch8aXEJ6qVgTnnYjKwrQ1BO3Tg28/F1h/FjMVQ=';
var comressedKeyB64 = 'AgmL07vrRR5lfkWuH1RAFJufx0B4J+BdOqIYZCH+fJc8';
const b64ToBin = (b64) => {
const binaryString = window.atob(b64);
const length = binaryString.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
};
const importB64Key = async (base64key) => {
const bin = b64ToBin(base64key);
console.log('bin ', bin);
const key = await window.crypto.subtle.importKey(
'raw',
bin,
{
name: 'ECDH',
namedCurve: 'P-256',
},
true,
[] // Fix: When importing a public key, an empty array must be passed for the key usages...
);
return key;
};
console.log(await importB64Key(uncomressedKeyB64));
console.log(await importB64Key(comressedKeyB64));
})();