更新于 2024-01-11
nodejs的ECDH算法在进行computeSecret的时候不会自动进行HASH运算,但C#的ECDH算法必须指定HASH算法。
两边算法必须使用相同的椭圆曲线和Hash算法,例如下面用例都是用的SHA256。
双方交换公钥,计算得到共享秘钥:SharedSecret。
用例使用readline去交换公钥,实际一般运行在其他协议中,像SSH2等。
单纯秘钥交换并不安全,需要增加其他手段保证不被中间人监听、修改,例如ssh2使用serverhostkey对秘钥交换的数据进行签名。
import readline from 'readline';
import crypto from 'crypto';
function computeHash(data, alg){
const hashAlg = crypto.createHash(alg || 'SHA256');
hashAlg.update(data);
return hashAlg.digest();
}
const reader = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const ecdh = crypto.createECDH('prime256v1');
const e = ecdh.generateKeys();
console.log("Local Public Key:" + e.toString('base64'));
reader.question('Please Input Remote PublicKey:', key => {
console.log("Remote Public Key:" + key);
let sharedSecret = ecdh.computeSecret(Buffer.from(key, 'base64'));
//计算共享秘钥,需要先HASH运算,以兼容C#
sharedSecret = computeHash(sharedSecret);
console.log("Shared Secret: " + sharedSecret.toString('base64'));
})
// 控制台输出
// Local Public Key:BGLb5Sddo8N5sd1/+a7vhwb66QFMqAJtNFvEISecSzrBP3DjEr3R6vHeefI100yVDCnPQLNmgv56ampL+tNBc2c=
// Please Input Remote PublicKey:BASkBjbrTNysfXrcqyCOWuVgWVRF8a3VTufPLl99PFdY4+oj2d2kImu1OoZLMMoRKwul02gP27dWRoMCGicHjuQ=
// Remote Public Key:BASkBjbrTNysfXrcqyCOWuVgWVRF8a3VTufPLl99PFdY4+oj2d2kImu1OoZLMMoRKwul02gP27dWRoMCGicHjuQ=
// Shared Secret: EvhnVuNRa3dBqIFDyfSQfYmRF4/fPgJHDRZG5xB6HnA=
var ecdh = ECDiffieHellmanHelper.New();
byte[] localPublicKey = ECDiffieHellmanHelper.CreateKeyExchange(ecdh);
Console.WriteLine("Local Public Key: {0}", localPublicKey.ToBase64String());
Console.WriteLine("Please Input Remote Public Key:");
string remotePublicKey = Console.ReadLine();
Console.WriteLine("Remote Public Key: {0}", remotePublicKey);
byte[] sharedSecret = ECDiffieHellmanHelper.DecryptKeyExchange(ecdh, remotePublicKey.AsBase64String());
Console.WriteLine("Shared Secret: {0}", sharedSecret.ToBase64String());
// 控制台输出
// Local Public Key: BASkBjbrTNysfXrcqyCOWuVgWVRF8a3VTufPLl99PFdY4+oj2d2kImu1OoZLMMoRKwul02gP27dWRoMCGicHjuQ=
// Please Input Remote Public Key:
// BGLb5Sddo8N5sd1/+a7vhwb66QFMqAJtNFvEISecSzrBP3DjEr3R6vHeefI100yVDCnPQLNmgv56ampL+tNBc2c=
// Remote Public Key: BGLb5Sddo8N5sd1/+a7vhwb66QFMqAJtNFvEISecSzrBP3DjEr3R6vHeefI100yVDCnPQLNmgv56ampL+tNBc2c=
// Shared Secret: EvhnVuNRa3dBqIFDyfSQfYmRF4/fPgJHDRZG5xB6HnA=
internal class ECPublicKey : ECDiffieHellmanPublicKey
{
private readonly ECParameters parameters;
public ECPublicKey(ECParameters parameters)
{
this.parameters = parameters;
}
public override ECParameters ExportParameters() => parameters;
public override ECParameters ExportExplicitParameters() => parameters;
}
public class ECDiffieHellmanHelper
{
//可以选择其他的曲线
private static readonly ECCurve curve = ECCurve.NamedCurves.nistP256;
public static ECDiffieHellman New(ECCurve curve) => ECDiffieHellman.Create(curve);
public static ECDiffieHellman New() => ECDiffieHellman.Create(curve);
public static byte[] CreateKeyExchange(ECDiffieHellman ecdh)
{
var args = ecdh.PublicKey.ExportParameters();
int blockSize = args.Q.X.Length;
byte[] buffer = new byte[blockSize * 2 + 1];
buffer[0] = 0x4;
args.Q.X.CopyTo(buffer, 1);
args.Q.Y.CopyTo(buffer, blockSize + 1);
return buffer;
}
public static byte[] DecryptKeyExchange(ECDiffieHellman ecdh, byte[] serverPublicKey)
{
ReadOnlySpan<byte> bytes = new(serverPublicKey);
int length = (serverPublicKey.Length - 1) / 2;
ECPublicKey thirdPublicKey = new (new ECParameters
{
Curve = curve,
D = null,
Q = new ECPoint
{
X = bytes.Slice(1, length).ToArray(),
Y = bytes.Slice(1 + length, length).ToArray(),
}
});
return ecdh.DeriveKeyFromHash(thirdPublicKey, HashAlgorithmName.SHA256);
}
}