I'm creating a web API and I need to implement ECDH for end-to-end encryption. On the server side I have a C # application and the client side a javascript application.
I can change the keys, generate the private keys and encrypt a message, but I can not decrypt it.
I think the problem is with public key exchange. In javascript the keys start with a byte "4" and in .NET the keys start with 8 bytes identifying the type and size of the key, to be able to import the keys I need to modify those initial bytes (Information that I found here ). This may cause some inconsistency.
On the client side I'm using the Web Cryptography API to perform the ECDH. And I'm implementing as below.
Generating the keys
await window.crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256",
},
false,
["deriveKey", "deriveBits"]
);
Exporting public keys:
await window.crypto.subtle.exportKey(
"raw",
publicKey
);
Importing external public keys
await window.crypto.subtle.importKey(
"raw",
{
name: "ECDH",
namedCurve: "P-256",
},
false,
["deriveKey", "deriveBits"]
)
And finally generating private keys
await window.crypto.subtle.deriveKey(
{
name: "ECDH",
namedCurve: "P-256",
public: publicKey,
},
privateKey,
{
name: "AES-CBC",
length: 256,
},
false,
["encrypt", "decrypt"]
)
On the server side I'm doing the same steps as follows. 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'm changing the first few bytes
public static byte[] GetPublicKey()
{
var auxKey = publicKey.Skip(7).ToArray();
auxKey[0] = 4;
return auxKey;
}
Importing the public key and generating the private key. Note that I'm changing the first few 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 believe the problem lies in these keys. Anyway I'm encrypting and decrypting as follows:
Javascript
async function encrypt2(iv, key, data){
var mensagemCriptografada;
await window.crypto.subtle.encrypt(
{
name: "AES-CBC",
iv: iv,
},
key,
str2ab(data) //Data é uma string e estou convertendo em base64 usando o método str2ab.
)
.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; //O IV é o mesmo usado pelo javascript
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
var dec = aes.CreateDecryptor(privateKey, iv);
var plain = dec.TransformFinalBlock(message, 0, message.Length);
//Tentei todos os Encodings possíveis.
decMessage = Encoding.UTF8.GetString(plain);
}
return decMessage;
I really have no idea how to solve this problem.