С# и PHP ECDH не совпадают

Я пытаюсь создать общий секрет между веб-сервером, на котором работает PHP, и настольным приложением С#. Я знаю о библиотеке BouncyCastle, но предпочел бы не использовать ее, так как она довольно большая.

Я использую phpecc и ECDiffieHellmanCng и пытаюсь сгенерировать общий секрет между двумя сторонами, но у меня проблемы с экспортом/импортом в C#.

Похоже, что phpecc требует формат der/pem для импорта ключа, а ECDiffieHellmanCng, похоже, не имеет простого способа экспортировать в совместимый формат.

Нужно ли мне писать свой собственный кодировщик и декодер pem/der, чтобы сделать это, или есть какой-то альтернативный более простой способ?

В настоящее время я делаю следующее на С#:

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


person UbbeLoL    schedule 09.05.2017    source источник


Ответы (1)


.NET Core 1.0 и .NET Framework 4.7 имеют ECParameters для импорта/экспорта ключей. Вызванный вами метод ToByteArray() создает CNG EccPublicBlob, который имеет мало общего с форматом SEC-1 ECParameters.

Я предполагаю, что вы хотели использовать secp384r1/NIST P-384, даже если вы указали его в качестве алгоритма хеширования. Если вы хотите какую-то другую кривую, вам нужно будет сделать несколько переводов.

Структура (.NET) ECParameters поможет вам только начать работу. Превращение этого в файл требует перевода его в структуру на основе ASN.1 с кодировкой PEM и кодировкой DER. (Но если вы придерживаетесь NIST P-256/384/521, вы можете сделать это с имеющимся у вас byte[])

В 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) байтов. Затем его нужно обернуть в БИТОВУЮ СТРОКУ, которая добавляет один байт полезной нагрузки, а также длину и тег. Итак, мы получаем:

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
person bartonjs    schedule 10.05.2017