I'm building a web API and I need to implement ECDH to perform an end-to-end cryptography. At the server side I have a C# application and at the client side I have a Javascript application.
I'm able to exchange the keys, generate the private keys and encrypt the messages, but I'm having problems decrypting it.
I think the problem is in the exchange of the public keys. In javascript keys starts with a byte "4" and .NET keys starts with 8 bytes identifying the type and the size of the key, I need to change this bytes in order to import each key (Information that I've found here). Maybe this cause some inconsistencies.
At the client side I'm using the Web Cryptography API to handle with the ECDH. I'm implementing as follows.
Generating the keys
await window.crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256",
},
false,
["deriveKey", "deriveBits"]
);
Exporting the public key like this:
await window.crypto.subtle.exportKey(
"raw",
publicKey
);
Importing the external public key
await window.crypto.subtle.importKey(
"raw",
{
name: "ECDH",
namedCurve: "P-256",
},
false,
["deriveKey", "deriveBits"]
)
And finally deriving the key
await window.crypto.subtle.deriveKey(
{
name: "ECDH",
namedCurve: "P-256",
public: publicKey,
},
privateKey,
{
name: "AES-CBC",
length: 256,
},
false,
["encrypt", "decrypt"]
)
At the server side I'm implementing the same steps as follow. Generating Public Key
private static ECDiffieHellmanCng ecdh = new ECDiffieHellmanCng(256);
public static void GeneratePublicKey()
{
ecdh.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
ecdh.HashAlgorithm = CngAlgorithm.Sha256;
publicKey = ecdh.PublicKey.ToByteArray();
}
Exporting public key. Note that I change the first bytes
public static byte[] GetPublicKey()
{
var auxKey = publicKey.Skip(7).ToArray();
auxKey[0] = 4;
return auxKey;
}
Importing public key and deriving private key. Note that I change the first bytes
public static void GerarChavePrivada(byte[] bobPublicKey)
{
byte[] aux = new byte[bobPublicKey.Length + 7];
aux[0] = 0x45;
aux[1] = 0x43;
aux[2] = 0x4B;
aux[3] = 0x31;
aux[4] = 0x20;
aux[5] = 0x00;
aux[6] = 0x00;
aux[7] = 0x00;
for (int i = 1; i < bobPublicKey.Length; i++)
{
aux[7 + i] = bobPublicKey[i];
}
var importedKey = CngKey.Import(aux, CngKeyBlobFormat.EccPublicBlob);
privateKey = ecdh.DeriveKeyMaterial(importedKey);
}
I belive the problem is about those keys. Any way these are the encrypt and decrypt codes:
Javascript
async function encrypt2(iv, key, data){
var mensagemCriptografada;
await window.crypto.subtle.encrypt(
{
name: "AES-CBC",
iv: iv,
},
key,
str2ab(data) //Data is a string and I'm converting using str2ab method.
)
.then(function(encrypted){
mensagemCriptografada = encrypted;
})
.catch(function(err){
console.error(err);
});
return mensagemCriptografada;
}
function str2ab (str) {
var array = new Uint8Array(str.length);
for(var i = 0; i < str.length; i++) {
array[i] = str.charCodeAt(i);
}
return array.buffer
}
C#
string decMessage = "";
using (Aes aes = new AesCryptoServiceProvider())
{
aes.Key = privateKey;
aes.IV = iv; //IV is the same used by the javascript code
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
var dec = aes.CreateDecryptor(privateKey, iv);
var plain = dec.TransformFinalBlock(message, 0, message.Length);
//I've tried every possible enconding.
decMessage = Encoding.UTF8.GetString(plain);
}
return decMessage;
I'm really don't have any idea how to resolve this problem.