2
I’m creating a web API and need to implement ECDH for end-to-end encryption. On the server side I have a C# application and on the client side a javascript application.
I can switch keys, generate private keys and encrypt a message, but I can’t decrypt it.
I think the problem is in the exchange of public keys. In javascript the keys start with a byte "4" and in . NET 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 I found here). Maybe this causes some inconsistency.
On the client side I am using the Web Cryptography API to perform ECDH. And I am implementing as below.
Generating the keys
await window.crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256",
},
false,
["deriveKey", "deriveBits"]
);
Exporting the 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 the 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 am 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 am changing the first 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 am changing 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 believe the problem lies in these keys. Anyway I am 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.