Posso extrair R e S de uma assinatura ECDSA em formato bit e vice-versa?

2

Eu pesquisei no fórum e encontrei informações relacionadas, mas não respondi a essa pergunta. Eu estou trabalhando em um ambiente extremamente restrito a largura de banda, na necessidade de criar o menor número possível de assinaturas pequenas de ECDSA. Estamos, por enquanto, usando o OpenSSL. Além disso - como os dados da assinatura devem ter um tamanho previsível - um tamanho fixo.

Eu entendo que a assinatura ECDSA consiste em dois inteiros S e R, do comprimento de bit igual ao tamanho da curva. Além disso, 6-7 bytes parecem ser adicionados quando tento criar uma assinatura, lendo o tamanho do comando cygwin wc -c . Eu li muitos lugares que essa sobrecarga pode variar - por isso perdemos a previsibilidade do tamanho da assinatura.

Solução possível - extrair S e R e transmitir está na forma binária (?) - exatamente como um fluxo de bits. Não tenho certeza se isso é possível, porque acho que ele perde a compatibilidade com a biblioteca OpenSSL. Portanto, precisaríamos reverter o processo, construindo uma assinatura usando o S e o R na codificação que o OpenSSL aceitará.

Tenho notado que existe uma opção -binary no OpenSSL que me deu um arquivo em branco. Eu presumi que estava no caminho errado.

    
por BenM 07.01.2016 / 15:51

1 resposta

2

Eu sei que é antigo, mas passei algum tempo tentando descobrir a mesma pergunta ultimamente, então aqui está uma espécie de resposta.

Vamos supor que você tenha criado uma assinatura assim:

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

Como a curva é de 160 bits, a assinatura é de dois números de 20 bytes. Por padrão, o OpenSSL irá gravá-lo no formato DER binário do ASN.1. Ao inspecioná-lo, devemos encontrar pelo menos 2 * 20 bytes:

$ 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

Nesta assinatura DER codificada, existem 47 bytes. Uma assinatura codificada DER ou PEM pode ser inspecionada com o comando asn1parse do OpenSSL para descobrir quais são os bytes:

$ 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

Em suma, diz:

  • No byte 0, há um cabeçalho DER de comprimento 2, indicando que há uma SEQUÊNCIA de elementos a serem seguidos, com um comprimento total combinado de 45 bytes.
  • No byte 2, há um cabeçalho DER de comprimento 2, indicando um elemento INTEGER de comprimento 20, o primeiro elemento da SEQUENCE (20 bytes são impressos em hex)
  • No byte 24, há um cabeçalho DER de comprimento 2, indicando um elemento INTEGER de comprimento 21, o segundo elemento da SEQUENCE (20 bytes são impressos em hex)

(O d = N significa apenas "profundidade", então os dois elementos INTEGER têm d == 1, porque são parte de uma sequência.)

Imprimindo bastante os bytes brutos de antes, pode-se reconhecer os elementos:

$ 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

Então, por que o segundo inteiro é 21 bytes?

Inteiros no DER são dois, então se o primeiro byte do inteiro de 20 bytes for > 0x7F, um byte extra de 0x00 é prefixado, assim o primeiro bit é sempre 0. Em outras palavras, enquanto R e S são 20 bytes números, codificando-os para inteiros DER podem levar um byte extra.

No OpenSSL, o tipo ECDSA_SIG contém dois BIGNUMs. Portanto, depois de decodificar os bytes DER com d2i_ECDSA_SIG , você poderá acessá-los com yourSig->r e yourSig->s , que serão < = 20 bytes (para curvas de 160 bits).

Também é possível que R ou S precisem de menos de 20 bytes, se os primeiros dígitos mais significativos forem zero. Você pode encontrar o número de bytes necessários para cada um com BN_num_bytes e, em seguida, gravá-los em uma matriz de bytes normal com BN_bn2bin para serialiazation.

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

No final do recebimento, você simplesmente recriaria a instância ECDSA_SIG definindo seus membros r e s :

ECDSA_SIG* ecSig = ECDSA_SIG_new();
BN_bin2bn( someBuffer,      20, ecSig->r );
BN_bin2bn( someBuffer + 20, 20, ecSig->s );
    
por 10.04.2016 / 23:24

Tags