
Ao mesmo tempo, a implementação de algoritmos criptográficos domésticos na biblioteca libgcrypt me inspirou muito. Tornou-se possível usar
esses algoritmos no Kleopatra e no Kmail e GnuPg em geral, considerar a biblioteca libgcrypt como uma
alternativa ao openssl com o mecanismo GOST. E tudo foi ótimo até a última sexta-feira.
Me pediram para verificar a assinatura eletrônica do GOST R 34.10-2012-256 para um documento criado no Microsoft Office no MS Windows. E eu decidi verificar no Kleopatra (eu tenho Linux). E o que você acha, a assinatura acabou errada. Dúvidas surgiram. Decidi verificar o
openssl com o mecanismo
GOST . A assinatura foi verificada com sucesso. Reescrevi urgentemente o arquivo no Kleopatra e ele não passou na verificação no MS Windows. Tentamos assinar e verificar outros arquivos, estava tudo bem. A questão era qual é o problema? Como o hash do documento está envolvido na assinatura, foi decidido verificar o cálculo do hash por diferentes programas. Primeiro,
implementações de código aberto para stribog estavam envolvidas :
E então houve um choque! O hash calculado pela "famosa implementação de Degtyarev" coincidiu com o hash calculado em openssl com o GOST do terminal, mas não coincidiu com o valor do hash calculado usando libgcrypt e libressl.
Quão
ru_crypt estava certo quando ele escreveu no começo de seu
artigo :
Eu imediatamente aviso que não verifiquei a exatidão das implementações.
A propósito, o padrão no GOST R 34.10-2012 também diz que exemplos de controle são apenas para referência. Deve-se entender claramente que os casos de teste não garantem que implementações diferentes produzam o mesmo resultado para todas as ocasiões.
Para calcular os valores de hash, foram utilizados os seguintes utilitários:
1) openssl
$ openssl dgst [–md_gost12_256|-md_gost12_512] <file>
2) libressl
$libressl dgst [–streebog256|streebog512] <file>
3) libgcrypt
$gchash [stribog256|stribog512] <file>
4) A famosa implementação de Degtyarev
$gost3411-2012 [-2|-5] <file>
Aqui está uma coisa interessante também: na transcrição latina, os stribogs escrevem stribog ou streebog. Seria bom chegar à uniformidade. E assim parece que essas são funções diferentes. Pessoalmente, prefiro a primeira opção - stribog.
Eu precisava de um árbitro.
Como árbitro, decidiu-se usar o token PKCS # 11 RUTOKEN EDS-2.0, que suporta os padrões criptográficos russos GOST R 34.10-2012, GOST R 34.11-2012, VKO GOST R 34.10-2012 (RFC 7836) com um comprimento de chave de 256 e 512 bits , e é certificado pelo FSB da Rússia como um meio de proteção de informações criptográficas (CPSI) e um meio de assinatura eletrônica.
Além disso, o token RUTOKEN EDS-2.0 é amplamente distribuído e possui muitos
certificados de loja para acesso a serviços do
Estado e outros portais.
Para calcular o valor do hash no token, usaremos o script test_digest.tcl no
Tcl :
test_digest.tcl #! /usr/bin/env tclsh package require pki lappend auto_path . package require pki::pkcs11 # PKCS#11 set pkcs11_module "/usr/local/lib64/librtpkcs11ecp_2.0.so" #set pkcs11_module "/usr/local/lib64/libls11sw2016.so" puts "Connect the Token and press Enter" gets stdin yes set handle [pki::pkcs11::loadmodule $pkcs11_module] set slots [pki::pkcs11::listslots $handle] foreach slotinfo $slots { set slotid [lindex $slotinfo 0] set slotlabel [lindex $slotinfo 1] set slotflags [lindex $slotinfo 2] if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} { set token_slotlabel $slotlabel set token_slotid $slotid # break } } proc usage {use error} { puts "Copyright(C) Orlov Vladimir (http://soft.lissi.ru) 2019" if {$use == 1} { puts $error puts "Usage:\ndigest <stribog256|stribog512> <file for digest>\n" } } set countcert [llength $argv] if { $countcert != 2 } { usage 1 "Bad usage!" exit } set digest_algo [lindex $argv 0] if {$digest_algo != "stribog256" && $digest_algo != "stribog512"} { usage 1 "Bad usage!" exit } set file [lindex $argv 1] if {![file exists $file]} { usage 1 "File $file not exist" exit } puts "Loading file for digest: $file" set fd [open $file] chan configure $fd -translation binary set cert_user [read $fd] close $fd if {$cert_user == "" } { usage 1 "Bad file: $file" exit } set aa [dict create pkcs11_handle $handle pkcs11_slotid $token_slotid] set digest_hex [pki::pkcs11::digest $digest_algo $cert_user $aa] puts "digest_hex=\n$digest_hex" exit
Quando essa discrepância na implementação aparece? Até o momento, foi possível determinar que essa discrepância ocorre ao calcular o hash dos arquivos doc criados no MS Office. Além disso, o hash dos primeiros 143 bytes é considerado o mesmo e, ao calcular o hash de 144 bytes, os valores são diferentes.
Os primeiros 143 bytes em hexadecimal são assim:
d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000002400000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Salve-os no arquivo Doc1_143_hex.txt.
Os primeiros 144 bytes em hexadecimal são assim:
d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000002400000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Salve-os no arquivo Doc1_144_hex.txt.
É conveniente usar o script hex2bin.tcl para converter da forma hexadecimal para a binária:
#!/usr/bin/tclsh proc usage {use error} { if {$use == 1} { puts $error puts "Usage:\nhex2bin <file with hex> <file for bin>\n" } } set countcert [llength $argv] if { $countcert != 2 } { usage 1 "Bad usage!" exit } set file [lindex $argv 0] if {![file exists $file]} { usage 1 "File $file not exist" exit } set fd [open $file] chan configure $fd -translation binary set cert_user [read $fd] close $fd if {$cert_user == "" } { usage 1 "Bad file with hex: $file" exit } set cert_user [binary format H* $cert_user] set fd [open [lindex $argv 1] w] chan configure $fd -translation binary puts -nonewline $fd $cert_user close $fd
Converta o código hexadecimal em binário:
$./hex2bin Doc1_143_hex.txt Doc1_143.bin $./hex2bin Doc1_144_hex.txt Doc1_144.bin $
Agora você pode verificar como o hash é calculado por várias implementações:
Primeiro, considere o hash do arquivo Doc1_143, bin:
$ ./openssl dgst -md_gost12_256 Doc1_143.bin md_gost12_256(Doc1_143.bin)= e63bd3edc44f9a03fece4198b690a8ae291b973ae61b2a0f512a9a7479431a63 $ ./libressl dgst -streebog256 Doc1_143.bin streebog256(Doc1_143.bin)= e63bd3edc44f9a03fece4198b690a8ae291b973ae61b2a0f512a9a7479431a63 $ ./gchash stribog256 Doc1_143.bin e63bd3edc44f9a03fece4198b690a8ae291b973ae61b2a0f512a9a7479431a63 Doc1_143.bin $ ./gost3411-2012 -2 Doc1_143.bin GOST R 34.11-2012 (Doc1_143.bin) = e63bd3edc44f9a03fece4198b690a8ae291b973ae61b2a0f512a9a7479431a63 $
Chegou o momento mais importante, o momento da verificação em um sistema certificado de proteção de informações criptográficas:
$ ./test_digest.tcl stribog256 Doc1_143.bin Connect the Token and press Enter Loading file for digest: Doc1_143.bin digest_hex= e63bd3edc44f9a03fece4198b690a8ae291b973ae61b2a0f512a9a7479431a63 $
Como você pode ver, tudo terminou para sempre.
Vamos ver o que acontece com o arquivo Doc1_144.bin:
$ ./openssl dgst -md_gost12_256 Doc1_144.bin md_gost12_256(Doc1_144.bin)= c766085540caaa8953bfcf7a1ba220619cee50d65dc242f82f23ba4b180b18e0 $ ./libressl dgst -streebog256 Doc1_144.bin streebog256(Doc1_144.bin)= 3965c99777eb1b64c783496fe950aa6540bc7baa399a3889995145afbdd76250 $
É isso, os valores dos hashes não coincidem. Para a pureza do experimento, verificamos as implementações restantes:
$ ./gchash_1.7.10 stribog256 Doc1_144.bin 3965c99777eb1b64c783496fe950aa6540bc7baa399a3889995145afbdd76250 Doc1_144.bin $ ./gost3411-2012 -2 Doc1_144.bin GOST R 34.11-2012 (Doc1_144.bin) = c766085540caaa8953bfcf7a1ba220619cee50d65dc242f82f23ba4b180b18e0 $ ./test_digest.tcl stribog256 Doc1_144.bin Connect the Token and press Enter Loading file for digest: Doc1_144.bin digest_hex= c766085540caaa8953bfcf7a1ba220619cee50d65dc242f82f23ba4b180b18e0 $
O hash calculado pela "famosa implementação Degtyarev" corresponde ao hash calculado no openssl com o mecanismo GOST, mas não corresponde ao valor do hash calculado usando libgcrypt e libressl.
Obtemos um resultado semelhante se considerarmos o hash stribog512.
Há uma conclusão. Se você deseja que a assinatura eletrônica GOST R 34.10-2012 gerada pelo libressl e libgcrypt (ou talvez outros) seja compatível com o openssl e, o mais importante, com a proteção de informações criptográficas certificada no sistema de certificação FSB da Rússia, use o verificado implementações para hashes de computação. Espero que esta publicação evite muitos mal-entendidos, e os autores da implementação do stribog no libressl, libgrypt e possivelmente outros ajudarão a eliminar essas discrepâncias. Hoje, devo admitir, nos produtos acima, na verdade não é o GOST R 34.10-2012 que está implementado, mas outra coisa. Este é um algoritmo diferente. O exemplo de teste fornecido provavelmente seria bom incluir como exemplo de teste para o GOST R 34.10-2012. E vou editar a libgcrypt para o Kleopatra e o KMail. A lenda de Cleopart e a criptografia russa estava inacabada.
PS O artigo já estava pronto quando meu colega disse que a discrepância entre as implementações aparece quando uma sequência suficientemente longa de 0xFF é encontrada. Ela, a propósito, está presente no início dos arquivos doc do MS Office. Eu verifiquei, do jeito que está. O arquivo continha 189 bytes.