ECDH public 和使用 .Net C# 生成私钥

ECDH public and private keys generation with .Net C#

我正在将 nodejs 代码移植到 .Net,我被困在需要生成 public 和私钥的这一部分。

Javascript代码:

const dh = crypto.createECDH('prime256v1');
let privk = dh.getPrivateKey();
let pubk = dh.getPublicKey();

我用 .Net C# 尝试了同样的方法

var ecdh = new ECDiffieHellmanCng(CngKey.Create(CngAlgorithm.ECDiffieHellmanP256, null, new CngKeyCreationParameters { ExportPolicy = CngExportPolicies.AllowPlaintextExport }));

var privateKey = ecdh.Key.Export(CngKeyBlobFormat.EccPrivateBlob);
var publickey = ecdh.Key.Export(CngKeyBlobFormat.EccPublicBlob);

然而,当我尝试将使用 C# 生成的那些密钥与 Google FCM 服务器交换时,出现无效参数错误。当我将生成的 byte[] 数组从 nodejs 复制到 .Net C# 代码作为常量时,它起作用了。很明显生成的密钥不符合服务器的要求。由于我使用的是未记录的界面,因此我不知道为什么不接受密钥。我可以看到使用 nodejs 生成的私钥长度为 32 字节,public 密钥长度为 65 字节。从 C# 生成的密钥长度为 140 和 96 字节。如何在C#中生成键来匹配nodejs中的键属性?

您可以使用 ECDiffieHellman 来加密消息。您有两个选择:静态-静态 ECDH 和静态-短暂 ECDH:

对于静态-静态 ECDH,接收方需要知道发送方的 public 密钥(这在您的应用程序中可能是也可能不是一个选项)。您还应该有一些对于此消息是唯一的数据(它可能是您从协议或数据库行中的其他地方获得的序列号或其他任何内容,或者它可能是随机数)。然后,您使用 ECDH 生成密钥并使用它来加密您的数据。这将为您提供所需的 16 字节加密数据长度,但它不是完全不对称的:加密器也能够解密消息(同样:这在您的应用程序中可能是也可能不是问题)。

静态临时密钥有点不同:这里加密器生成临时(临时)EC 密钥对。然后,他将这个密钥对与接收方的 public 密钥一起使用,生成一个可用于加密数据的密钥。最后,他将临时密钥对中的 public 密钥与加密数据一起发送给接收方。这可能更适合您的应用程序,但是使用 ECDH-256 和 AES 的完整加密数据现在将是 2*32+16=80 字节(正如 GregS 指出的那样,您可以通过仅发送 public-键,但我不认为 .NET 公开了重新计算 y 坐标的功能)。

这是一个小型的 class,可以执行静态-静态 ECDH:

public static class StaticStaticDiffieHellman
{
  private static Aes DeriveKeyAndIv(ECDiffieHellmanCng privateKey, ECDiffieHellmanPublicKey publicKey, byte[] nonce)
  {
    privateKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
    privateKey.HashAlgorithm = CngAlgorithm.Sha256;
    privateKey.SecretAppend = nonce;
    byte[] keyAndIv = privateKey.DeriveKeyMaterial(publicKey);
    byte[] key = new byte[16];
    Array.Copy(keyAndIv, 0, key, 0, 16);
    byte[] iv = new byte[16];
    Array.Copy(keyAndIv, 16, iv, 0, 16);

    Aes aes = new AesManaged();
    aes.Key = key;
    aes.IV = iv;
    aes.Mode = CipherMode.CBC;
    aes.Padding = PaddingMode.PKCS7;

    return aes;
  }

  public static byte[] Encrypt(ECDiffieHellmanCng privateKey, ECDiffieHellmanPublicKey publicKey, byte[] nonce, byte[] data){
    Aes aes = DeriveKeyAndIv(privateKey, publicKey, nonce);
    return aes.CreateEncryptor().TransformFinalBlock(data, 0, data.Length);
  }

  public static byte[] Decrypt(ECDiffieHellmanCng privateKey, ECDiffieHellmanPublicKey publicKey, byte[] nonce, byte[] encryptedData){
    Aes aes = DeriveKeyAndIv(privateKey, publicKey, nonce);
    return aes.CreateDecryptor().TransformFinalBlock(encryptedData,0, encryptedData.Length);
  }
}

// Usage:

ECDiffieHellmanCng key1 = new ECDiffieHellmanCng();    
ECDiffieHellmanCng key2 = new ECDiffieHellmanCng();

byte[] data = Encoding.UTF8.GetBytes("TestTestTestTes");
byte[] nonce = Encoding.UTF8.GetBytes("whatever");

byte[] encryptedData = StaticStaticDiffieHellman.Encrypt(key1, key2.PublicKey, nonce, data);

Console.WriteLine(encryptedData.Length); // 16

byte[] decryptedData = StaticStaticDiffieHellman.Decrypt(key2, key1.PublicKey, nonce, encryptedData);

Console.WriteLine(Encoding.UTF8.GetString(decryptedData));

我能够使用 Bouncy Castle 解决我的问题

            ECKeyPairGenerator gen = new ECKeyPairGenerator("ECDH");
            SecureRandom secureRandom = new SecureRandom();
            X9ECParameters ecp = NistNamedCurves.GetByName("P-256");
            ECDomainParameters ecSpec = new ECDomainParameters(ecp.Curve, ecp.G, ecp.N, ecp.H, ecp.GetSeed());
            ECKeyGenerationParameters ecgp = new ECKeyGenerationParameters(ecSpec, secureRandom);
            gen.Init(ecgp);
            AsymmetricCipherKeyPair eckp = gen.GenerateKeyPair();

            ECPublicKeyParameters ecPub = (ECPublicKeyParameters)eckp.Public;
            ECPrivateKeyParameters ecPri = (ECPrivateKeyParameters)eckp.Private;

            byte[] publicKeyBytes = ecPub.Q.GetEncoded();