Ошибки подпрограмм кодирования ASN1 при проверке типа подписи ECDSA с помощью openssl - PullRequest
0 голосов
/ 25 января 2020

Я пытаюсь проверить цифровую подпись SHA256 ECDSA, предоставленную нам внешней стороной. Они сами проверили процесс подписания, но мы потерпели неудачу в наших попытках. Мы постоянно получаем asn1 encoding routines ошибки во время проверки openssl, но я не могу увидеть, что не так с подписью или нашим процессом.

Вот тестовая установка ... Ключ Publi c (pubkey.pem) :

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOorVp0M8xien/r1/1Ln7TkSpzzcX
BL/MGRz66J1HSlEgBD5FwwpO1vo6jf/9azcrrrDdCi2NH9/cSDfv5D8gTA==
-----END PUBLIC KEY-----

Подписываемое сообщение представляет собой текстовую строку:

HELLO

Цифровая подпись (signature.sig):

JJhwReHev8cxOsNKCR5t/Ee3WU9c7tkf9RuGNamXdpXQu9OL8ZKnsrblCO7vEmOXGKGrk6NsgA5JZpQhXO3A1Q==

Общий подход, который мы Мы взяли:

# create message file
echo "HELLO" > hello.txt

#VERIFY
openssl dgst -sha256 -verify pubkey.pem -signature signature.sig hello.txt

и ответ

Error Verifying Data
4655195756:error:0DFFF09B:asn1 encoding routines:CRYPTO_internal:too long:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/crypto/asn1/asn1_lib.c:143:
4655195756:error:0DFFF066:asn1 encoding routines:CRYPTO_internal:bad object header:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/crypto/asn1/tasn_dec.c:1113:
4655195756:error:0DFFF03A:asn1 encoding routines:CRYPTO_internal:nested asn1 error:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/crypto/asn1/tasn_dec.c:306:Type=ECDSA_SIG

В качестве альтернативы мы используем base64, кодирующую подпись base64 -D signature.sig > signature.bin, но получаем те же ответы об ошибках. Я также пытался использовать openssl pkeyutl, но это также приводит к ошибкам asn1 encoding routines. Использование ans1parse для анализа подписи дает:

openssl asn1parse -in signature.bin
Error: offset too large

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

Ответы [ 2 ]

4 голосов
/ 25 января 2020

Ваш файл signature.sig представляется в кодировке base64. Расшифруйте его так:

$ base64 -d signature.sig >signature.bin

Давайте посмотрим, что у нас есть:

$ hexdump -C signature.bin
00000000  24 98 70 45 e1 de bf c7  31 3a c3 4a 09 1e 6d fc  |$.pE....1:.J..m.|
00000010  47 b7 59 4f 5c ee d9 1f  f5 1b 86 35 a9 97 76 95  |G.YO\......5..v.|
00000020  d0 bb d3 8b f1 92 a7 b2  b6 e5 08 ee ef 12 63 97  |..............c.|
00000030  18 a1 ab 93 a3 6c 80 0e  49 66 94 21 5c ed c0 d5  |.....l..If.!\...|
00000040

Для сравнения я создал новый закрытый ключ ECDSA на основе той же кривой, что и ваш ключ publi c использует (P-256):

$ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out key.pem

И затем подписывает некоторые данные, используя их:

$ echo "HELLO" > hello.txt
$ openssl dgst -sha256 -sign key.pem -out hello.sig hello.txt
$ openssl asn1parse -in hello.sig -inform DER
    0:d=0  hl=2 l=  68 cons: SEQUENCE          
    2:d=1  hl=2 l=  32 prim: INTEGER           :2C1599C7765B047A2E98E2265CF6DB91232200559909D7F97CA3E859A39AC02C
   36:d=1  hl=2 l=  32 prim: INTEGER           :14E748DF692A8A7A2E41F984497782FF03F970DDB6591CCC68C71704B959A480

Итак, вы заметите, что у нас есть два целых числа в последовательности где каждое целое число имеет длину ровно 32 байта. Это соответствует определению ECDSA_SIG ASN.1:

ECDSA-Sig-Value ::= SEQUENCE { r INTEGER, s INTEGER }

Необработанная сигнатура ECDSA состоит из двух целых чисел «r» и «s». OpenSSL ожидает, что они будут заключены в кодированное представление DER. Однако, как вы уже обнаружили, что у вас есть для подписи, не является действительным DER. Он равен , но ровно 64 байта в длину - что предполагает, что он состоит из 2-х 32-байтовых целых чисел, соединенных вместе.

Для целей этого упражнения мы можем использовать шестнадцатеричный редактор для преобразования необработанного r и значения в формате DER. Давайте посмотрим на hexdump файла hello.sig, который я создал ранее:

$ hexdump -C hello.sig
00000000  30 44 02 20 2c 15 99 c7  76 5b 04 7a 2e 98 e2 26  |0D. ,...v[.z...&|
00000010  5c f6 db 91 23 22 00 55  99 09 d7 f9 7c a3 e8 59  |\...#".U....|..Y|
00000020  a3 9a c0 2c 02 20 14 e7  48 df 69 2a 8a 7a 2e 41  |...,. ..H.i*.z.A|
00000030  f9 84 49 77 82 ff 03 f9  70 dd b6 59 1c cc 68 c7  |..Iw....p..Y..h.|
00000040  17 04 b9 59 a4 80                                 |...Y..|
00000046

Мы начнем с 30, который говорит нам, что у нас есть последовательность. Следующий байт 44 - это длина оставшихся данных. Далее следует 02, который является тегом для целого числа, за которым следует 20 (который равен 32 в десятичном виде), который является длиной целого числа. Следующие 32 байта представляют собой целое число (значение r). Затем у нас есть еще 02 байт (целое число) и 20 (длина 32), за которыми следуют 32 байта значения s.

Так что, если мы добавим байты 30 44 02 20 вперед из ваших двоичных данных подписи, за которыми следуют первые 32 байта данных, затем 02 20, за которыми следуют следующие 32 байта, мы должны получить то, что хотим ...

... за исключением, к сожалению, это не совсем так просто. В вашем s значении есть осложнение. Вы заметите, что он начинается с байта d0. Этот байт имеет наиболее значимый установленный бит, который в кодировании DER целого числа указывает, что целочисленное значение является отрицательным. Это не то, что мы хотим. Чтобы обойти это, мы должны добавить дополнительный 00 байт в начало значения s.

Это изменяет общую длину, поэтому теперь мы должны добавить эти байты в начало 30 45 02 20 за ними следуют первые 32 байта данных подписи, затем 02 21 00, за которыми следуют следующие 32 байта данных подписи. Я сделал это в шестнадцатеричном редакторе и придумал следующее:

$ hexdump -C signature2.bin
00000000  30 45 02 20 24 98 70 45  e1 de bf c7 31 3a c3 4a  |0E. $.pE....1:.J|
00000010  09 1e 6d fc 47 b7 59 4f  5c ee d9 1f f5 1b 86 35  |..m.G.YO\......5|
00000020  a9 97 76 95 02 21 00 d0  bb d3 8b f1 92 a7 b2 b6  |..v..!..........|
00000030  e5 08 ee ef 12 63 97 18  a1 ab 93 a3 6c 80 0e 49  |.....c......l..I|
00000040  66 94 21 5c ed c0 d5                              |f.!\...|
00000047

Давайте проверим, выглядит ли это вменяемым:

$ openssl asn1parse -in signature2.bin -inform DER
    0:d=0  hl=2 l=  69 cons: SEQUENCE          
    2:d=1  hl=2 l=  32 prim: INTEGER           :24987045E1DEBFC7313AC34A091E6DFC47B7594F5CEED91FF51B8635A9977695
   36:d=1  hl=2 l=  33 prim: INTEGER           :D0BBD38BF192A7B2B6E508EEEF12639718A1AB93A36C800E496694215CEDC0D5

Теперь давайте попробуем проверить подпись:

$ openssl dgst -sha256 -verify pubkey.pem -signature signature2.bin hello.txt
Verification Failure

Черт. Так близко и еще так далеко. Но по крайней мере мы избавились от ошибок ASN.1. Так почему это не работает? По догадке я сделал это:

echo -n "HELLO" > hello2.txt

Аргумент "-n" для эха подавляет переводы строк с выхода. Возможно, новую строку не следует включать в данные, которые будут перевариваться для подписи. Итак, попробуем это:

$ openssl dgst -sha256 -verify pubkey.pem -signature signature2.bin hello2.txt
Verified OK

Удачи!

1 голос
/ 25 января 2020

То, что у вас есть, это так называемая плоская подпись, состоящая из значений R и S - поскольку подпись состоит из кортежа (R, S). Эти числа кодируются как два статических размера без знака, целые числа с прямым порядком байтов с таким же размером, что и размер ключа.

Однако OpenSSL ожидает два значения INTEGER в кодировке ASN.1 / DER в последовательности. Это два динамических размера со знаком, значениями с прямым порядком байтов (в том же порядке). Таким образом, вам нужно перекодировать подпись, чтобы она стала действительной.

Преобразование между ними сравнительно легко, но командная строка OpenSSL, похоже, не поддерживает это напрямую. Поэтому я бы порекомендовал приложение Perl, Python или C.


Например, Python 3 (минус обработка файла, извините):

from array import array
import base64

def encodeLength(vsize) -> bytearray:
    tlv = bytearray()
    if (vsize < 128):
        tlv.append(vsize)
    elif (vsize < 256):
        tlv.append(0x81)
        tlv.append(vsize)
    else:
        raise
    return tlv

def encodeInteger(i) -> bytearray:
    signedSize = (i.bit_length() + 8) // 8
    value = i.to_bytes(signedSize, byteorder='big', signed = True)

    tlv = bytearray()
    tlv.append(0x02)
    tlv += encodeLength(len(value))
    tlv += value
    return tlv

def encodeSequence(value) -> bytearray:
    tlv = bytearray()
    tlv.append(0x30)
    tlv += encodeLength(len(value))
    tlv += value
    return tlv

# test only

bin = base64.b64decode("JJhwReHev8cxOsNKCR5t/Ee3WU9c7tkf9RuGNamXdpXQu9OL8ZKnsrblCO7vEmOXGKGrk6NsgA5JZpQhXO3A1Q==")

# size of the curve (not always a multiple of 8!)
keysize = 256
csize = (keysize + 8 - 1) // 8
if (len(bin) != 2 * csize):
    raise
r = int.from_bytes(bin[0:csize], byteorder='big', signed = False)
s = int.from_bytes(bin[csize:csize * 2], byteorder='big', signed = False)

renc = encodeInteger(r)
senc = encodeInteger(s)
rsenc = encodeSequence(renc + senc)

print(base64.b64encode(rsenc))
...