【问题标题】:ECDH public and private keys generation with .Net C#使用 .Net C# 生成 ECDH 公钥和私钥
【发布时间】:2020-12-08 17:58:02
【问题描述】:

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

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 字节,私钥长度为 65 字节,公钥长度为 65 字节。从 C# 生成的密钥长度分别为 140 和 96 个字节。如何在 C# 中生成键来匹配 nodejs 中的键属性?

【问题讨论】:

标签: c# .net cryptography bouncycastle


【解决方案1】:

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

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

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

这是一个小类,将做静态-静态 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));

【讨论】:

    【解决方案2】:

    我能够使用 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();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-11
      • 2015-12-28
      • 1970-01-01
      • 2020-02-14
      • 2012-04-05
      • 2022-01-13
      • 2019-10-12
      • 1970-01-01
      相关资源
      最近更新 更多