3

Я искал форум и нашел соответствующую информацию, но не ответил на этот вопрос. Я работаю в среде с крайне ограниченной пропускной способностью, и мне нужно создать как можно меньше подписей ECDSA. На данный момент мы используем OpenSSL. Также - поскольку данные подписи должны иметь предсказуемую длину - фиксированный размер.

Я понимаю, что сигнатура ECDSA состоит из двух целых чисел S и R, длина битов которых равна размеру кривой. Кроме того, кажется, что при создании подписи добавляется 6-7 байт, считывая размер с помощью команды cygwin wc -c . Я читал во многих местах, что эти издержки могут варьироваться - поэтому мы теряем предсказуемость длины подписи.

Возможное решение - извлечь S и R и передать в двоичном виде (?) Форма - просто как поток битов. Я не уверен, возможно ли это, потому что я предполагаю, что он теряет совместимость с библиотекой OpenSSL. Таким образом, нам нужно будет повернуть процесс вспять, построив сигнатуру, используя S и R в кодировке, которую OpenSSL примет.

Я заметил, что в OpenSSL есть опция -binary которая дает мне пустой файл. Я предположил, что был на неправильном пути.

1 ответ1

4

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

Давайте предположим, что вы создали подпись примерно так:

$ openssl dgst -sha256 -sign private_secp160k1.pem foo.txt > sign.bin

Поскольку кривая составляет 160 бит, сигнатура состоит из двух 20-байтовых чисел. По умолчанию OpenSSL записывает его в двоичном формате DER ASN.1. Осматривая его, мы должны найти не менее 2 * 20 байт:

$ xxd -i < sign.bin
0x30, 0x2d, 0x02, 0x14, 0x22, 0xd0, 0x8b, 0xc1, 0x0d, 0x0b, 0x7b, 0xff,
0xe6, 0xc1, 0x77, 0xc1, 0xdc, 0xc4, 0x2f, 0x64, 0x05, 0x17, 0x71, 0xc8,
0x02, 0x15, 0x00, 0xdd, 0xf4, 0x67, 0x10, 0x39, 0x92, 0x1b, 0x13, 0xf2,
0x40, 0x20, 0xcd, 0x15, 0xe7, 0x6a, 0x63, 0x0b, 0x86, 0x07, 0xb6

В этой конкретной кодированной подписи DER есть 47 байтов. Подпись в кодировке DER или PEM может быть проверена с помощью команды OpenSSL asn1parse чтобы выяснить, что это за байты:

$ openssl asn1parse -inform DER -in sign.bin
 0:d=0  hl=2 l=  45 cons: SEQUENCE          
 2:d=1  hl=2 l=  20 prim: INTEGER  :22D08BC10D0B7BFFE6C177C1DCC42F64051771C8
24:d=1  hl=2 l=  21 prim: INTEGER  :DDF4671039921B13F24020CD15E76A630B8607B6

Короче говоря, это говорит:

  • В байте 0 имеется заголовок DER длиной 2, указывающий на последовательность SEQUENCE элементов, которые должны следовать общей суммарной длиной 45 байтов.
  • В байте 2 есть заголовок DER длины 2, указывающий элемент INTEGER длины 20, первый элемент последовательности (20 байтов затем печатаются в шестнадцатеричном формате)
  • В байте 24 имеется заголовок DER длины 2, указывающий элемент INTEGER длины 21, второй элемент последовательности (20 байтов затем печатаются в шестнадцатеричном формате)

(D = N означает "глубина", поэтому два элемента INTEGER имеют d == 1, потому что они являются частью последовательности.)

Довольно печатая необработанные байты из ранее, можно распознать элементы:

$ xxd -i < sign.bin
0x30, 0x2d, # Sequence of length 45 to follow (45 == 0x2d)

0x02, 0x14, # Integer of length 20 to follow (20 == 0x14)
# Here come the 20 bytes:
0x22, 0xd0, 0x8b, 0xc1, 0x0d, 0x0b, 0x7b, 0xff, 0xe6, 0xc1, 
0x77, 0xc1, 0xdc, 0xc4, 0x2f, 0x64, 0x05, 0x17, 0x71, 0xc8,

0x02, 0x15, # Integer of length 21 to follow (21 == 0x15)
0x00,       # The first of the 21 integer bytes (see explanation below!)
# Here come the remaining 20 bytes
0xdd, 0xf4, 0x67, 0x10, 0x39, 0x92, 0x1b, 0x13, 0xf2, 0x40, 
0x20, 0xcd, 0x15, 0xe7, 0x6a, 0x63, 0x0b, 0x86, 0x07, 0xb6

Так почему же второе целое число 21 байт?

Целые числа в DER являются дополнением до двух, поэтому, если первый байт 20-байтового целого числа> 0x7F, добавляется дополнительный байт 0x00, таким образом, первый бит всегда равен 0. Другими словами, хотя R и S представляют собой 20-байтовые числа, их кодирование в целые числа DER может занять дополнительный байт.

В OpenSSL тип ECDSA_SIG содержит два BIGNUM. Поэтому, как только вы декодировали байты DER с помощью d2i_ECDSA_SIG , вы можете получить к ним доступ с помощью yourSig->r и yourSig->s , которые будут <= 20 байтов (для кривых 160 битов).

Также возможно, что R или S потребуется менее 20 байтов, если их первые значащие цифры окажутся равными нулю. Вы можете найти количество байтов, необходимое для каждого с BN_num_bytes , а затем записать их в обычный байтовый массив с BN_bn2bin для сериализации.

ECDSA_SIG* ecSig = d2i_ECDSA_SIG(NULL, derBuf, derBufSize);

int rBytes = BN_num_bytes(ecSig->r), // Number of bytes needed for R
    sBytes = BN_num_bytes(ecSig->s); // Number of bytes needed for S

// ...
unsigned char someBuffer[40] = {0};
BN_bn2bin( ecSig->r, someBuffer + 20 - rBytes ); // Place R first in the buffer
BN_bn2bin( ecSig->s, someBuffer + 40 - sBytes ); // Place S last in the buffer

На приемном конце вы просто воссоздаете экземпляр ECDSA_SIG, устанавливая его члены r и s :

ECDSA_SIG* ecSig = ECDSA_SIG_new();
BN_bin2bn( someBuffer,      20, ecSig->r );
BN_bin2bn( someBuffer + 20, 20, ecSig->s );

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .