Об open-source реализациях хэш-функции ГОСТ Р 34.11-2012 и их влиянии на электронную подпись ГОСТ Р 34.10-2012

    В свое время реализация отечественных криптографических алгоритмов в библиотеке libgcrypt очень меня вдохновила. Стало возможным задействовать эти алгоритмы и в Kleopatra и в Kmail и в GnuPg в целом, рассматривать библиотеку libgcrypt как алтернативу openssl с ГОСТ-ым engine. И все было замечательно до прошлой пятницы.

    Меня попросили проверить электронную подпись ГОСТ Р 34.10-2012-256 для документа, созданного в Microsoft Office на MS Windows. И я ее решил проверить в Kleopatra (у меня стоит Linux). И что вы думаете, подпись оказалась неверной. Закрались сомнения. Решил проверить на openssl с ГОСТ-овым engine. Подпись успешно была проверена. Срочно переподписал файл в Kleopatra и он не прошел проверку на MS Windows. Попробовали другие файлы подписать и проверить, все было нормально. Встал вопрос в чем беда? Поскольку при подписании участвует хэш документа, было решено проверить вычисление хэш разными программами. Прежде всего была задействован open-source реализации для stribog:


    И тут случился шок! Хэш, подсчитанный «знаменитой реализацией Дегтярева» совпадал с хэшом, подсчитанным в openssl с ГОСТ-ым endine, но не совпадал со значением хэш, посчитанным с помощью libgcrypt и libressl.

    Как был прав ru_crypt, когда в начале своей статьи написал:
    Сразу предупреждаю, что корректность реализаций не проверял.

    Кстати, в самом стандарте на ГОСТ Р 34.10-2012 тоже написано, что контрольные примеры носят справочный характер. Надо четко понимать, что контрольные примеры не гарантирует, что разные реализации дают один и тот же результат на все случаи жизни.

    Для вычисления хэш-значений использовались утилиты следующего вида:

    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) Знаменитая реализация Дегтярева

    $gost3411-2012 [-2|-5] <file>

    Вот тоже интересная вещь: в латинской транскрипции стрибог пишут то stribog, то streebog. Неплохо бы прийти к единообразию. А так кажется, что это разные функции. Лично мне предпочтительней первый вариант – stribog.

    Нужен был третейский судья.

    В качестве третейского судьи было решено использовать токен PKCS#11 РУТОКЕН ЭЦП-2.0, который поддерживает российские криптографические стандарты ГОСТ Р 34.10-2012, ГОСТ Р 34.11-2012, VKO ГОСТ Р 34.10-2012 (RFC 7836) с длиной ключа 256 и 512 бит, и сертифицирован ФСБ России как средство криптографической защиты информации (СКЗИ) и средство электронной подписи.

    К тому же токен РУТОКЕН ЭЦП-2.0 широко распространен и многие на нем хранят сертификаты для доступа на Госуслуги и другие порталы.
    Для вычисления значения хэша на токене воспользуемся скриптом test_digest.tcl на языке 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


    Когда проявляется это расхождение в реализации? Пока что удалось определить, что данное расхождение возникает при подсчете хэш doc-файлов, созданных в MS Office. Причем хэш от первых 143 байтов считается одинаково, а уже при подсчете хэш от 144 байт значения получаются разные.

    Первые 143 байта в шестнадцатиричном виде выглядят так:

    d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000002400000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

    Сохраним их в файле Doc1_143_hex.txt.

    Первые 144 байта в шестнадцатиричном виде выглядят так:

    d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000002400000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

    Сохраним их в файле Doc1_144_hex.txt.

    Для перевода из шестнадцатеричного вида в бинарный удобно воспользоваться скриптом hex2bin.tcl:

    #!/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

    Преобразуем шестнадцатеричный код в бинарный:
    $./hex2bin Doc1_143_hex.txt Doc1_143.bin
    $./hex2bin Doc1_144_hex.txt Doc1_144.bin
    $

    Теперь можно проверить как вычисляется хэш различными реализациями:
    Сначало считаем хэш для файла 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 
    $

    Наступил самый важный момент, момент проверки на сертифицированном СКЗИ:

    $ ./test_digest.tcl  stribog256 Doc1_143.bin  
    Connect the Token and press Enter 
    Loading file for digest: Doc1_143.bin 
    digest_hex=
    e63bd3edc44f9a03fece4198b690a8ae291b973ae61b2a0f512a9a7479431a63 
    $

    Как видим, все завершилось для хорошо.

    Посмотрим, что будет для файла 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 
    $

    Все, значения хэшей не совпадают. Для чистоты эксперимента проверим и оставшиеся реализации:

    $ ./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 
    $

    Хэш, подсчитанный «знаменитой реализацией Дегтярева» совпадает с хэшом, подсчитанным в openssl с ГОСТ-овым engine, но не совпадает со значением хэша, посчитанным с помощью libgcrypt и libressl.

    Аналогичный результат мы получим, если будем считать хэш stribog512.

    Вывод один. Если вы хотите, чтобы электронная подпись ГОСТ Р 34.10-2012, формируемая средствами libressl и libgcrypt (а может и другими), была совместима с реализацией в openssl и, самое главное, с реализацией в СКЗИ, сертифицированных в системе сертификации ФСБ России, используйте проверенные реализации для вычисления хэшей. Надеюсь, эта публикация позволит избежать многих недоразумений, а авторам реализации stribog в libressl, libgrypt и возможно других поможет устранить эти расхождения. Сегодня, надо признать, в вышеназванных продуктах фактически реализован не ГОСТ Р 34.10-2012 а что-то другое. Это другой алгоритм. Приведенный тестовый пример, наверное, неплохо было бы включить как тестовый пример для ГОСТ Р 34.10-2012. А я иду править libgcrypt для Kleopatra и KMail. Сказание о Клеопарте и российской криптографии оказалось неоконченным.

    P.S. Статья уже была готова, когда мой коллега сказал, что расхождение реализаций проявляются тогда, когда встречается достаточно длинная последовательность 0xFF. Она, эта последовательность, кстати, присутствует в начале файлов doc от MS Office. Я проверил, так оно и есть. Файл содержал 189 байт.
    Поделиться публикацией

    Похожие публикации

    Комментарии 18

      0
      Если я правильно помню, то ГОСТ Р 34.11-2012 ещё, к огромному сожалению, не определяет порядок байт, т.е. спецификация дана в терминах 512-битных чисел и не фиксируется как они должны преобразовываться в байты. Конечно, традиционно в криптографии используется big-endian порядок, но определённость здесь бы не помешала…

      Можете ли вы дать тестовые вектора которым желательно следовать проверяя реализации? Мне бы очень пригодилось для вот этого проекта.
        +1
        Примеры на тестовых векторах есть в самом ГОСТ, например здесь docs.cntd.ru/document/gost-r-34-11-2012 смотреть Приложение А.
          0
          Эти вектора я уже использовал, но, если я правильно понимаю, они не покрывают ошибку рассматриваемую в статье и могут интерпретироваться с обоими порядками байтов.
            0

            Это же ГОСТ Р 34.11-2012, а не ГОСТ Р 34.11-94. Все в ГОСТ.

        +3

        Похоже, ссылка «Знаменитая реализация Дегтярева» в статье неверная — она указывает на https://github.com/be1ay/MyHash_Stribog (причём там как раз лежит код с ошибкой), однако, скорее всего, имелся в виду репозиторий https://github.com/adegtyarev/streebog.


        Как ни удивительно, похоже, в данном случае программисты не смогли правильно реализовать вроде бы простейшую операцию — «длинное» сложение: в некоторых случаях неправильно обрабатывается перенос в старшую часть числа.


        В реализации Дегтярева проблема устранена в декабре 2017 года, а позднее код слегка оптимизирован.


        В gost-engine для OpenSSL проблема исправлена в марте 2018 года (исправление совпадает с первым вариантом патча для реализации Дегтярева).


        В LibreSSL ошибка пока на месте, в libgcrypt эта ошибка тоже присутствует (у этих проектов соответствующий кусок кода совпадает, и существенно отличается от реализации Дегтярева).

          0

          Спасибо за замечания и существенные дополнения.


          Как ни удивительно, похоже, в данном случае программисты не смогли правильно реализовать вроде бы простейшую операцию — «длинное» сложение: в некоторых случаях неправильно обрабатывается перенос в старшую часть числа.

          Мы тоже пришли к такому выводу. И сейчас тестируем правки. Спасибо.

            –4
            Свастика-то зачем на рисунке?
              +1

              Не понял. Это одно из стилистических представлений бога Стрибог.

                +2
                Коловрат — название свастики, имеющее якобы древнеславянское происхождение и используемое неоязычниками и некоторыми неонацистами.
                  +1
                  Ну, эта геометрия имеет как бы не праиндоевропейское происхождение. А что используется некоторыми неонацистами — так неонацисты чего только не используют.
              0
              Вот тоже интересное вещь: в латинской транскрипции стрибог пишут то stribog, то streebog. Неплохо бы прийти к единообразию.

              Разработчики пишут «Streebog»:
              agora.guru.ru/csr2012/files/6.pdf
                0

                Перевод мне не нравится. Я выразил свою точку зрения и не более того.

                +1
                PyGOST (http://pygost.cypherpunks.ru/ — реализация на Python), к слову, успешно проходит данный тест: git.cypherpunks.ru/cgit.cgi/pygost.git/commit/?id=739b2be4cbdbf29726d132c1fd1679590ad7957f
                  0

                  Приятно слышать, спасибо. Мы тоже проверивели Stribog еще и на Ruby . Там тоже все хорошо, только учитывать порядок байт не забывать.

                  0

                  Спасибо за статью, но самое главное в практическом смысле это не статья на хабре, а багрепорты и/или пуллреквесты

                    0

                    Спасибо. Но я не согласен с тем, что эта не статья на хабр. А как же люди узнают, что есть ошибки где-то и чем можно воспользоваться, чтобы их избежать? Или как надо тестировать и т.д. Если продукт не выложен для всеобщего доступа, не входит в состав официальных дистрибутивов, особенно российских, тогда да, можно обойтись багрепортами и/или пуллреквестами. Здесь же другая ситуация!
                    За доброе слово еще раз спасибо.

                    0

                    Проверили реализацию хэш-функции Streebog (ГОСТ 34.11-2012) в Linux 5.0. Все нормально.

                      0

                      Пришло сообщение от lumag :


                      Спасибо, исправлено.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое