I'm trying to replicate the functionality of the following C# code in TypeScript, which generates an ECDH key pair and computes a shared secret. The C# code is as follows:
public static byte[] CreateECDHAddHockKey(byte[] productKey, ref byte[] publicKey) {
//productKey is byte[64]
//BCRYPT_ECDH_PUBLIC_P256_MAGIC = 0x314B4345,
//var keyType = new byte[] { 0x45, 0x43, 0x53, 0x31 };
//var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 };
byte[] PublicP256Header = new byte[] { 0x45, 0x43, 0x4B, 0x31, 0x20, 0x00, 0x00, 0x00 }; //8 bytes
byte[] completeKey = new byte[productKey.Length + PublicP256Header.Length]; //8+64 = 72 bytes
Array.Copy(PublicP256Header, completeKey, PublicP256Header.Length);
Array.Copy(productKey, 0, completeKey, PublicP256Header.Length, productKey.Length);
CngKey key = CngKey.Import(completeKey, CngKeyBlobFormat.EccPublicBlob);
ECDiffieHellmanCng MyKey = new ECDiffieHellmanCng(256);
MyKey.HashAlgorithm = CngAlgorithm.Sha256;
MyKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
var sharedSecret = MyKey.DeriveKeyMaterial(key); //32 bytes
publicKey = MyKey.Key.Export(CngKeyBlobFormat.EccPublicBlob).Skip(8).ToArray(); //64 bytes
return sharedSecret;
}
I've attempted to replicate the functionality in TypeScript using the crypto module, but I'm facing format errors and differences in output lengths. This is the closest code I have so far:
import crypto, { KeyObject } from 'crypto';
function generateKeyPairSync(data: Uint8Array): {
publicKey: KeyObject;
sharedSecret: KeyObject;
} {
//generate key pair using ecc public blob
const keyPair = crypto.generateKeyPairSync('ed25519', {
publicKeyEncoding: {
type: 'spki',
format: 'der',
data,
},
});
return {
publicKey: keyPair.publicKey,
sharedSecret: keyPair.privateKey,
};
}
Ed25519 and ECDH are different algorithms. Ed25519 applies the curve edwards25519 and is used for signing/verification. ECDH operates on other curves, in your case NIST P-256 (aka prime256v1 aka secp256r1) and is intended to generate a shared secret.
Regarding the public key, it should be noted that the C# code imports/exports a raw key
x|y(64 bytes), while NodeJS expects the uncompressed key0x04|x|y(65 bytes). Accordingly, the leading 0x04 byte is to be removed or added depending on the context.The 0x45434B3120000000 prefix is part of the proprietary MS key format and is not required for the NodeJS side.
Regarding C#, keep in mind that
DeriveKeyMaterial()does not return the actual shared secret (i.e. the x coordinate of the calculated EC point), but the SHA256 hash of the shared secret, which is why on the NodeJS side this hash has to be determined as well (since SHA256 is not reversible, the C# implementation does not allow the determination of the actual shared secret).NodeJS sample implementation for the generation/import of an ECDH key pair and for the determination of the SHA256 hash of the shared secret:
Test:
When the C# code is executed with the public key of the NodeJS side:
the SHA256 hash of the shared secret and public key of the C# side results in, for instance:
When the NodeJS code is executed with the public key of the C# side:
the SHA256 hash of the shared secret results in:
in accordance with the value of the C# side.