C#和PHP ECDH不匹配

时间:2017-05-09 13:20:37

标签: c# php diffie-hellman

我正在尝试在运行PHP的Web服务器和C#桌面应用程序之间生成共享密钥。我知道BouncyCastle库,但我不想使用它,因为它非常庞大。

我正在使用phpeccECDiffieHellmanCng并尝试在双方之间生成共享密钥,但我在使用C#导出/导入时遇到问题。

似乎phpecc需要der / pem格式才能导入密钥,而ECDiffieHellmanCng似乎没有任何简单的方法以兼容格式导出。

我是否需要编写自己的pem / der编码器和解码器才能执行此操作或者是否有其他更简单的方法?

目前我在C#中执行以下操作:

using (var ecdh = new ECDiffieHellmanCng())
        {
            ecdh.HashAlgorithm = CngAlgorithm.ECDiffieHellmanP384;
            ecdh.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;

            var encoded = EncodePem(ecdh.PublicKey.ToByteArray()); 
            //... do something with encoded
        }

private static string EncodePem(byte[] data)
    {
        var pemDat = new StringBuilder();
        var chunk = new char[64];

        pemDat.AppendLine("-----BEGIN PUBLIC KEY-----");

        var encodedData = Convert.ToBase64String(data);
        for (var i = 0; i < encodedData.Length; i += chunk.Length)
        {
            var index = 0;
            while (index != chunk.Length && i + index < encodedData.Length)
            {
                chunk[index] = encodedData[i + index];
                index++;
            }
            pemDat.AppendLine(new string(chunk));
        }

        pemDat.AppendLine("-----END PUBLIC KEY-----");
        return pemDat.ToString();
    }

显然上面只是进行pem编码,所以在php端它会在尝试解析它时返回错误:

  

输入:运行时

     

异常消息:数据无效。

     

文件:/.../ vendor / mdanter / ecc / src / Serializer / PublicKey / Der / Parser.php

     

行:49

1 个答案:

答案 0 :(得分:1)

.NET Core 1.0和.NET Framework 4.7具有导入/导出密钥的ECParameters结构。您调用的ToByteArray()方法生成的CNG EccPublicBlob与SEC-1 ECParameters格式几乎没有关系。

我将假设您想要使用secp384r1 / NIST P-384,即使您将其指定为哈希算法。如果你想要一些其他曲线,你需要做一些翻译。

(.NET)ECParameters结构只会帮助您入门。将其转换为文件需要将其转换为PEM编码的DER编码的基于ASN.1的结构。 (但如果你坚持使用NIST P-256/384/521,你可以用你现有的字节[]来做到这一点)

SEC 1 v2.0中我们得到以下结构:

SubjectPublicKeyInfo ::= SEQUENCE {
  algorithm AlgorithmIdentifier {{ECPKAlgorithms}} (WITH COMPONENTS {algorithm, parameters}),
  subjectPublicKey BIT STRING
}

ECPKAlgorithms ALGORITHM ::= {
  ecPublicKeyType |
  ecPublicKeyTypeRestricted |
  ecPublicKeyTypeSupplemented |
  {OID ecdh PARMS ECDomainParameters {{SECGCurveNames}}} |
  {OID ecmqv PARMS ECDomainParameters {{SECGCurveNames}}},
  ...
}

ecPublicKeyType ALGORITHM ::= {
  OID id-ecPublicKey PARMS ECDomainParameters {{SECGCurveNames}}
}

ECDomainParameters{ECDOMAIN:IOSet} ::= CHOICE {
  specified SpecifiedECDomain,
  named ECDOMAIN.&id({IOSet}),
  implicitCA NULL
}

An elliptic curve point itself is represented by the following type
  ECPoint ::= OCTET STRING
whose value is the octet string obtained from the conversion routines given in Section 2.3.3.

将其提取到相关部分,您需要编写

SEQUENCE (SubjectPublicKeyInfo)
  SEQUENCE (AlgorithmIdentifier)
    OBJECT IDENTIFIER id-ecPublicKey
    OBJECT IDENTIFIER secp384r1 (or whatever named curve you're using)
  BIT STRING
    public key encoded as ECPoint

AlgorithmIdentifier包含固定的数据,因为您不更改曲线:

SEQUENCE (AlgorithmIdentifier)
30 xx [yy [zz]]
   OBJECT IDENTIFIER id-ecPublicKey (1.2.840.10045.2.1)
   06 07 2A 86 48 CE 3D 02 01
   OBJECT IDENTIFIER secp384r1 (1.3.132.0.34)
   06 05 2B 81 04 00 22

我们现在可以计算有效载荷中有多少字节:16(0x10),所以我们填写长度:

30 10 06 07  2A 86 48 CE   3D 02 01 06  05 2B 81 04
00 22

每个人都能理解的公钥编码是“未压缩点”,即

04 th eb yt es of x. th eb yt es of y.

事实证明,对于给定的曲线,它也具有固定的大小,因此与大多数DER编码的东西不同,您可以在一次传递中执行此操作:)。对于secp384r1,x和y坐标各自为384位值,或(384 + 7)/ 8 == 48字节,因此ECPoint为48 + 48 + 1 == 97(0x61)字节。然后它需要包装在BIT STRING中,它会添加一个有效负载字节以及长度和标记。所以,我们得到:

private static byte[] s_secp384r1PublicPrefix = {
    // SEQUENCE (SubjectPublicKeyInfo, 0x76 bytes)
    0x30, 0x76,
    // SEQUENCE (AlgorithmIdentifier, 0x10 bytes)
    0x30, 0x10,
    // OBJECT IDENTIFIER (id-ecPublicKey)
    0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
    // OBJECT IDENTIFIER (secp384r1)
    0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22,
    // BIT STRING, 0x61 content bytes, 0 unused bits.
    0x03, 0x62, 0x00,
    // Uncompressed EC point
    0x04,
}

...

using (ECDiffieHellman ecdh = ECDiffieHellman.Create())
{
    ecdh.KeySize = 384;

    byte[] prefix = s_secp384r1PublicPrefix;
    byte[] derPublicKey = new byte[120];
    Buffer.BlockCopy(prefix, 0, derPublicKey, 0, prefix.Length);

    byte[] cngBlob = ecdh.PublicKey.ToByteArray();
    Debug.Assert(cngBlob.Length == 104);

    Buffer.BlockCopy(cngBlob, 8, derPublicKey, prefix.Length, cngBlob.Length - 8);

    // Now move it to PEM
    StringBuilder builder = new StringBuilder();
    builder.AppendLine("-----BEGIN PUBLIC KEY-----");
    builder.AppendLine(
        Convert.ToBase64String(derPublicKey, Base64FormattingOptions.InsertLineBreaks));
    builder.AppendLine("-----END PUBLIC KEY-----");

    Console.WriteLine(builder.ToString());
}

运行我从中获得的输出到OpenSSL:

$ openssl ec -pubin -text -noout
read EC key
(paste)
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEwpbxYmcsNvr14D8k+0VQCkSY4WCV/3V10AiIq7sFdmUX
9+0DMuuLDmcKjL1ZFEFk0yHCPpY+pdkYtzPwE+dsApCPT3Ljk0AxHQBTSo4yjwsElMoA4Mtp8Qdo
LZD1Nx6v
-----END PUBLIC KEY-----
Private-Key: (384 bit)
pub:
    04:c2:96:f1:62:67:2c:36:fa:f5:e0:3f:24:fb:45:
    50:0a:44:98:e1:60:95:ff:75:75:d0:08:88:ab:bb:
    05:76:65:17:f7:ed:03:32:eb:8b:0e:67:0a:8c:bd:
    59:14:41:64:d3:21:c2:3e:96:3e:a5:d9:18:b7:33:
    f0:13:e7:6c:02:90:8f:4f:72:e3:93:40:31:1d:00:
    53:4a:8e:32:8f:0b:04:94:ca:00:e0:cb:69:f1:07:
    68:2d:90:f5:37:1e:af
ASN1 OID: secp384r1
NIST CURVE: P-384