Как преобразовать данные подписи, закодированные в ECDSA DER, в формат, поддерживаемый Microsoft CNG? - PullRequest
0 голосов
/ 08 июня 2018

Я готовлю минидрайвер к выполнению входа в смарт-карту с использованием функции NCryptSignHash Microsoft CNG.

Когда я выполняю вход с помощью ключа EC SECP521R1 в смарт-карте, он генерирует данные знака длиной 139 в качестве ECCформат данных со знаком:

ECDSASignature ::= SEQUENCE {
    r   INTEGER,
    s   INTEGER
}

Пример данных со знаком:

308188024201A2001E9C0151C55BCA188F201020A84180B339E61EDE61F6EAD0B277321CAB81C87DAFC2AC65D542D0D0B01C3C5E25E9209C47CFDDFD5BBCAFA0D2AF2E7FD86701024200C103E534BD1378D8B6F5652FB058F7D5045615DCD940462ED0F923073076EF581210D0DD95BF2891358F5F743DB2EC009A0608CEFAA9A40AF41718881D0A26A7F4

Но когда я выполняю Sign с использованием MS_KEY_STORAGE_PROVIDER, он генерирует знак длиной 132 байта.

Какова процедура для уменьшения размера данных знака с 139 до 132?

Ответы [ 2 ]

0 голосов
/ 11 июня 2018

Ваш ввод - это формат подписи X9.62, который представляет собой ПОСЛЕДОВАТЕЛЬНОСТЬ, содержащую две подписи в кодировке ASN.1 / DER.Эти целые числа имеют переменные размеры, числа со знаком и порядковые номера.Они кодируются в минимальном количестве байтов.Это означает, что размер кодировки может варьироваться.

139 байтов являются общими, поскольку они предполагают максимальный размер кодирования для r и s.Эти значения вычисляются с использованием модульной арифметики, и поэтому они могут содержать любое количество битов, вплоть до количества битов порядка n, которое совпадает с размером ключа, 521 бит.


132 байта определены в стандарте ISO / IEC 7816-8 / IEEE P1363, который является стандартом, касающимся подписей для смарт-карт.Подпись состоит из конкатенации r и s, где r и s кодируются как минимальное количество байтов для отображения значения того же размера, что и порядок, в байтах.r и s имеют статические размеры, без знака, числа с прямым порядком байтов.

Вычисление количества байтов r или s равно ceil((double) n / 8) или (n + 8 - 1) / 8, где 8 равноколичество бит в байте.Таким образом, если эллиптическая кривая составляет 521 бит, то результирующий размер составляет 66 байтов, и вместе они, следовательно, потребляют 132 байта.


Теперь перейдем к декодированию.Существует несколько способов справиться с этим: выполнить полный синтаксический анализ ASN.1, получить целые числа и затем снова закодировать их в форме ISO 7816-8. Это наиболее логичный способ.

Однако вы также можете увидетьчто вы можете просто скопировать байты как r и s всегда будут неотрицательными (и, следовательно, без знака) и с прямым порядком байтов.Так что вам просто нужно компенсировать размер.В противном случае единственная сложная часть - это возможность декодировать длину компонентов в структуре X9.62.


Предупреждение : код на C # вместо C ++, как я ожидал,основной язык .NET;язык, не указанный в вопросе, когда я писал основную часть ответа.

class ConvertECDSASignature
{
    private static int BYTE_SIZE_BITS = 8;
    private static byte ASN1_SEQUENCE = 0x30;
    private static byte ASN1_INTEGER = 0x02;

    public static byte[] lightweightConvertSignatureFromX9_62ToISO7816_8(int orderInBits, byte[] x9_62)
    {
        int offset = 0;
        if (x9_62[offset++] != ASN1_SEQUENCE)
        {
            throw new IllegalSignatureFormatException("Input is not a SEQUENCE");
        }

        int sequenceSize = parseLength(x9_62, offset, out offset);
        int sequenceValueOffset = offset;

        int nBytes = (orderInBits + BYTE_SIZE_BITS - 1) / BYTE_SIZE_BITS;
        byte[] iso7816_8 = new byte[2 * nBytes];

        // retrieve and copy r

        if (x9_62[offset++] != ASN1_INTEGER)
        {
            throw new IllegalSignatureFormatException("Input is not an INTEGER");
        }

        int rSize = parseLength(x9_62, offset, out offset);
        copyToStatic(x9_62, offset, rSize, iso7816_8, 0, nBytes);

        offset += rSize;

        // --- retrieve and copy s

        if (x9_62[offset++] != ASN1_INTEGER)
        {
            throw new IllegalSignatureFormatException("Input is not an INTEGER");
        }

        int sSize = parseLength(x9_62, offset, out offset);
        copyToStatic(x9_62, offset, sSize, iso7816_8, nBytes, nBytes);

        offset += sSize;

        if (offset != sequenceValueOffset + sequenceSize)
        {
            throw new IllegalSignatureFormatException("SEQUENCE is either too small or too large for the encoding of r and s"); 
        }

        return iso7816_8;
    }

    /**
     * Copies an variable sized, signed, big endian number to an array as static sized, unsigned, big endian number.
     * Assumes that the iso7816_8 buffer is zeroized from the iso7816_8Offset for nBytes.
     */
    private static void copyToStatic(byte[] sint, int sintOffset, int sintSize, byte[] iso7816_8, int iso7816_8Offset, int nBytes)
    {
        // if the integer starts with zero, then skip it
        if (sint[sintOffset] == 0x00)
        {
            sintOffset++;
            sintSize--;
        }

        // after skipping the zero byte then the integer must fit
        if (sintSize > nBytes)
        {
            throw new IllegalSignatureFormatException("Number format of r or s too large");
        }

        // copy it into the right place
        Array.Copy(sint, sintOffset, iso7816_8, iso7816_8Offset + nBytes - sintSize, sintSize);
    }

    /*
     * Standalone BER decoding of length value, up to 2^31 -1.
     */
    private static int parseLength(byte[] input, int startOffset, out int offset)
    {
        offset = startOffset;
        byte l1 = input[offset++];
        // --- return value of single byte length encoding
        if (l1 < 0x80)
        {
            return l1;
        }

        // otherwise the first byte of the length specifies the number of encoding bytes that follows
        int end = offset + l1 & 0x7F;

        uint result = 0;

        // --- skip leftmost zero bytes (for BER)
        while (offset < end)
        {
            if (input[offset] != 0x00)
            {
                break;
            }
            offset++;
        }

        // --- test against maximum value
        if (end - offset > sizeof(uint))
        {
            throw new IllegalSignatureFormatException("Length of TLV is too large");
        }

        // --- parse multi byte length encoding
        while (offset < end)
        {
            result = (result << BYTE_SIZE_BITS) ^ input[offset++];
        }

        // --- make sure that the uint isn't larger than an int can handle
        if (result > Int32.MaxValue)
        {
            throw new IllegalSignatureFormatException("Length of TLV is too large");
        }

        // --- return multi byte length encoding
        return (int) result;
    }
}

Обратите внимание, что код является несколько допустимым в том смысле, что он не требует кодирования длины минимальная для кодирования длины SEQUENCE и INTEGER (что и должно быть).

Это также позволяет неправильно кодировать значения INTEGER, которые неоправданно дополняются слева нулевыми байтами.

Ни одна из этих проблем не должна нарушать безопасностьалгоритма, но другие библиотеки могут и должны быть менее разрешительными.

0 голосов
/ 11 июня 2018

Какова процедура для уменьшения размера данных знака со 139 до 132?

У вас есть подпись в кодировке ASN.1 (показана ниже).Используется Java, OpenSSL и некоторыми другими библиотеками.Вам нужна подпись в формате P1363, которая представляет собой конкатенацию r || s без кодировки ASN.1.P1363 используется Crypto ++ и несколькими другими библиотеками.(Существует еще один распространенный формат подписи - OpenPGP).

Для объединения r || s значения r и s должны составлять 66 байтов из-за размера элемента поля secp-521r1 в октете.граница.Это означает, что процедура заключается в том, что вы должны убрать внешний SEQUENCE, а затем обрезать два INTEGER, а затем объединить значения двух целых чисел.

Ваша отформатированная r || s подпись с использованием вашего образцаданные будут:

01 A2 00 1E ... 7F D8 67 01 || 00 C1 03 E5 ... 0A 26 A7 F4

Microsoft .Net 2.0 имеет классы ASN.1, которые позволяют вам манипулировать данными, закодированными в ASN.1.См. класс AsnEncodedData .


$ echo 08188024201A2001E9C0151C55BCA188F201020A84180B339E61EDE61F6EAD0B277321CAB
81C87DAFC2AC65D542D0D0B01C3C5E25E9209C47CFDDFD5BBCAFA0D2AF2E7FD86701024200C103E5
34BD1378D8B6F5652FB058F7D5045615DCD940462ED0F923073076EF581210D0DD95BF2891358F5F
743DB2EC009A0608CEFAA9A40AF41718881D0A26A7F4 | xxd -r -p > signature.bin

$ dumpasn1 signature.bin
  0 136: SEQUENCE {
  3  66:   INTEGER
       :     01 A2 00 1E 9C 01 51 C5 5B CA 18 8F 20 10 20 A8
       :     41 80 B3 39 E6 1E DE 61 F6 EA D0 B2 77 32 1C AB
       :     81 C8 7D AF C2 AC 65 D5 42 D0 D0 B0 1C 3C 5E 25
       :     E9 20 9C 47 CF DD FD 5B BC AF A0 D2 AF 2E 7F D8
       :     67 01
 71  66:   INTEGER
       :     00 C1 03 E5 34 BD 13 78 D8 B6 F5 65 2F B0 58 F7
       :     D5 04 56 15 DC D9 40 46 2E D0 F9 23 07 30 76 EF
       :     58 12 10 D0 DD 95 BF 28 91 35 8F 5F 74 3D B2 EC
       :     00 9A 06 08 CE FA A9 A4 0A F4 17 18 88 1D 0A 26
       :     A7 F4
       :   }

0 warnings, 0 errors.

Еще один примечательный момент: .Net использует формат XML, подробно описанный в RFC 3275, Синтаксис и обработка XML-подписи.Это формат, отличный от ASN.1, P1363, OpenPGP, CNG и других библиотек.

Преобразование ASN.1 в P1363 довольно тривиально.Вы можете увидеть пример использования библиотеки Crypto ++ на ECDSA, подписать с BouncyCastle и проверить с помощью Crypto ++ .

. Может оказаться полезным Криптографическая совместимость: Цифровые подписи в Code Project.

...