Могу ли я извлечь R и S из сигнатуры ECDSA в битовой форме, и наоборот?
Я искал форум и нашел соответствующую информацию, но не ответил на этот вопрос. Я работаю в среде с крайне ограниченной пропускной способностью, и мне нужно создать как можно меньше подписей ECDSA. На данный момент мы используем OpenSSL. Также - поскольку данные подписи должны иметь предсказуемую длину - фиксированный размер.
Я понимаю, что сигнатура ECDSA состоит из двух целых чисел S и R, длина битов которых равна размеру кривой. Кроме того, 6-7 байт, кажется, добавляются, когда я пытаюсь создать подпись, читая размер из cygwin wc -c
команда. Я читал много мест, где эти издержки могут варьироваться - поэтому мы теряем предсказуемость длины подписи.
Возможное решение - извлечь S и R и передать в двоичном (?) Виде - как поток битов. Я не уверен, возможно ли это, потому что я предполагаю, что он теряет совместимость с библиотекой OpenSSL. Таким образом, нам нужно будет повернуть процесс вспять, создав сигнатуру с использованием S и R в кодировке, которую OpenSSL примет.
Я заметил, что есть -binary
вариант в OpenSSL, который дал мне пустой файл. Я предположил, что был на неправильном пути.
1 ответ
Я знаю, что он старый, но я потратил некоторое время, пытаясь выяснить тот же вопрос в последнее время, так что вот такой ответ.
Предположим, вы создали подпись примерно так:
$ 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 байтов. С помощью OpenSSL можно проверить подпись в кодировке DER или PEM. 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 );