Как правильно создавать цифровые подписи?Могу ли я использовать DSA_sign_setup ()? - PullRequest
0 голосов
/ 08 июня 2018

Я работаю над приложением, производительность которого критична.

В этом приложении у меня много сообщений (т. Е. Несколько тысяч), которые необходимо подписать (и, конечно, проверить) отдельно одним и тем же закрытым ключом / открытым ключом.Я использую библиотеку OpenSSL.

Наивный подход с функциями DSA (см. Ниже) займет десятки секунд, чтобы подписать, что нехорошо.Я попытался использовать функцию DSA_sign_setup(), чтобы ускорить процесс, но я не могу найти правильный способ ее использования.

Я также попробовал ECDSA, но я потерял в получении правильной конфигурации.

Как правильно сделать это, если я действительно беспокоюсь об эффективности?

#include <openssl/dsa.h>
#include <openssl/engine.h>
#include <stdio.h>
#include <openssl/evp.h>

int N=3000;

int main()
{
    DSA *set=DSA_new();
    int a;
    a=DSA_generate_parameters_ex(set,1024,NULL,1,NULL,NULL,NULL);
    printf("%d\n",a);
    a=DSA_generate_key(set);
    printf("%d\n",a);
    unsigned char msg[]="I am watching you!I am watching you!";
    unsigned char sign[256];
    unsigned int size;
    for(int i=0;i<N;i++)
        a=DSA_sign(1,msg,32,sign,&size,set);
    printf("%d %d\n",a,size);
}

Ответы [ 3 ]

0 голосов
/ 08 июня 2018

Я решил удалить этот ответ, потому что он ставит под угрозу усилия команды OpenSSL по обеспечению безопасности их программного обеспечения.

Отправленный мною код все еще виден, если вы посмотрите на правку, но НЕ делайтеИспользуйте его, это не безопасно .Если вы это сделаете, вы рискуете раскрыть свой личный ключ .

Пожалуйста, не говорите, что вас не предупреждали.Фактически, воспринимайте это как предупреждение , если вы используете DSA_sign_setup() в своем собственном коде, потому что вы не должны этого делать.Ответ Ромена выше содержит более подробную информацию об этом.Спасибо.

0 голосов
/ 11 июня 2018

Использование DSA_sign_setup() способом, предложенным выше, на самом деле совершенно небезопасно, и, к счастью, разработчики OpenSSL сделали структуру DSA непрозрачной, чтобы разработчики не могли попытаться прорваться.

DSA_sign_setup() генерирует новый случайный одноразовый номер(это своего рода эфемерный ключ на подпись).Он никогда не должен использоваться повторно под тем же долгосрочным секретным ключом. Никогда .

Теоретически все еще можно относительно безопасно повторно использовать один и тот же одноразовый номер для того же сообщения, но как только одна и та же комбинация личного ключа и одноразового номера будет повторно использована для двух разных сообщений, которые вы простораскрыть всю информацию, которая необходима злоумышленнику для получения вашего секретного ключа (см. Sony fail0verflow , что в основном связано с той же ошибкой повторного использования одноразового номера с ECDSA).

К сожалению, DSA работает медленно, особенно теперь, когда требуются более длинные ключи: для ускорения работы вашего приложения вы можете попробовать использовать ECDSA (например, с кривой NISTP256, по-прежнему без повторного использования одноразового номера) или Ed25519 (детерминированный одноразовый номер).


Подтверждение концепции с использованием EVP_DigestSign API

Обновление: - вот подтверждение концепции того, как программно генерировать подписи с OpenSSL.Предпочтительным способом является использование EVP_DigestSign API , поскольку он абстрагируется от того, какой тип асимметричного ключа используется.

В следующем примере расширяется PoC в этой вики-странице OpenSSL: я протестировал эту работу, используя закрытый ключ DSA или NIST P-256, с OpenSSL 1.0.2, 1.1.0 и 1.1.1-pre6.

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/evp.h>

#define KEYFILE "private_key.pem"
#define N 3000
#define BUFFSIZE 80

EVP_PKEY *read_secret_key_from_file(const char * fname)
{
    EVP_PKEY *key = NULL;
    FILE *fp = fopen(fname, "r");
    if(!fp) {
        perror(fname); return NULL;
    }
    key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
    fclose(fp);
    return key;
}

int do_sign(EVP_PKEY *key, const unsigned char *msg, const size_t mlen,
            unsigned char **sig, size_t *slen)
{
    EVP_MD_CTX *mdctx = NULL;
    int ret = 0;

    /* Create the Message Digest Context */
    if(!(mdctx = EVP_MD_CTX_create())) goto err;

    /* Initialise the DigestSign operation - SHA-256 has been selected
     * as the message digest function in this example */
    if(1 != EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, key))
        goto err;

    /* Call update with the message */
    if(1 != EVP_DigestSignUpdate(mdctx, msg, mlen)) goto err;

    /* Finalise the DigestSign operation */
    /* First call EVP_DigestSignFinal with a NULL sig parameter to
     * obtain the length of the signature. Length is returned in slen */
    if(1 != EVP_DigestSignFinal(mdctx, NULL, slen)) goto err;
    /* Allocate memory for the signature based on size in slen */
    if(!(*sig = OPENSSL_malloc(*slen))) goto err;
    /* Obtain the signature */
    if(1 != EVP_DigestSignFinal(mdctx, *sig, slen)) goto err;

    /* Success */
    ret = 1;

err:
    if(ret != 1)
    {
        /* Do some error handling */
    }

    /* Clean up */
    if(*sig && !ret) OPENSSL_free(*sig);
    if(mdctx) EVP_MD_CTX_destroy(mdctx);

    return ret;
}

int main()
{
    int ret = EXIT_FAILURE;
    const char *str = "I am watching you!I am watching you!";
    unsigned char *sig = NULL;
    size_t slen = 0;
    unsigned char msg[BUFFSIZE];
    size_t mlen = 0;

    EVP_PKEY *key = read_secret_key_from_file(KEYFILE);
    if(!key) goto err;

    for(int i=0;i<N;i++) {
        if ( snprintf((char *)msg, BUFFSIZE, "%s %d", str, i+1) < 0 )
            goto err;
        mlen = strlen((const char*)msg);
        if (!do_sign(key, msg, mlen, &sig, &slen)) goto err;
        OPENSSL_free(sig); sig = NULL;
        printf("\"%s\" -> siglen=%lu\n", msg, slen);
    }

    printf("DONE\n");
    ret = EXIT_SUCCESS;

err:
    if (ret != EXIT_SUCCESS) {
        ERR_print_errors_fp(stderr);
        fprintf(stderr, "Something broke!\n");
    }

    if (key)
        EVP_PKEY_free(key);

    exit(ret);
}

Генерация ключа:

# Generate a new NIST P-256 private key
openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem

Производительность / Случайность

Я запустил как ваш оригинальный пример, так и мой код на моем (Intel Skylake) компьютере и на Raspberry Pi 3. В обоих случаях ваш оригинальныйПример не занимает десятки секунд.Учитывая, что вы, очевидно, видите значительное улучшение производительности при использовании подхода небезопасного DSA_sign_setup() в OpenSSL 1.0.2 (который внутренне потребляет новую случайность, в дополнение к некоторой довольно дорогой модульной арифметике), я подозреваю, что вы могли бы на самом делеесть проблема с PRNG, которая замедляет генерацию новых случайных одноразовых номеров и оказывает большее влияние, чем модульные арифметические операции.Если это так, то вы можете определенно извлечь выгоду из использования Ed25519, поскольку в этом случае одноразовый номер является детерминированным, а не случайным (он генерируется с использованием безопасных хеш-функций и сочетания закрытого ключа и сообщения).К сожалению, это означает, что вам нужно будет дождаться выпуска OpenSSL 1.1.1 (надеюсь, этим летом).

On Ed25519

Для использования Ed25519 (который будет поддерживаться изначально, начиная с OpenSSL 1.1.1) приведенный выше пример необходимо изменить, так как в OpenSSL 1.1.1 отсутствует поддержка Ed25519ph, и вместо использования потокового API Init / Update / Final вам потребуется вызывать одноразовыйEVP_DigestSign() интерфейс (см. документацию ).

Полный отказ от ответственности : следующий абзац - бесстыдная заглушка для моего libsuola исследованияпроект, поскольку я определенно мог бы извлечь выгоду из тестирования реальных приложений от других пользователей .

В качестве альтернативы, если вы не можете ждать, я являюсь разработчиком OpenSSL ENGINE под названием libsuola, который добавляетподдержка Ed25519 в OpenSSL 1.0.2, 1.1.0 (а также 1.1.1 с использованием альтернативных реализаций).Он все еще экспериментальный, но он использует сторонние реализации (libsodium, HACL *, donna) для криптографии, и пока мое тестирование (в исследовательских целях) еще не выявило выдающихся ошибок.

Сравнительное сравнение OPоригинальный пример и мой

Чтобы обратиться к некоторым комментариям, я скомпилировал и выполнил оригинальный пример OP, слегка измененную версию, исправляющую некоторые ошибки и утечки памяти, и мой пример того, как использовать EVP_DigestSign API, всескомпилирован с использованием OpenSSL 1.1.0h (скомпилирован как общая библиотека с пользовательским префиксом из архива выпуска с параметрами конфигурации по умолчанию).

Полную информацию можно найти в этом гисте , который включает в себя точные версии, которые я тестировал, Makefile, содержащий все подробности о том, как были скомпилированы примеры и как выполнялся тест, и сведения о моеммашина (кратко, это четырехъядерный процессор i5-6500 @ 3.20 ГГц, а масштабирование частоты / Turbo Boost отключено из программного обеспечения и из UEFI).

Как видно из make_output.txt:

Running ./op_example
time ./op_example >/dev/null
0.32user 0.00system 0:00.32elapsed 100%CPU (0avgtext+0avgdata 3452maxresident)k
0inputs+0outputs (0major+153minor)pagefaults 0swaps

Running ./dsa_example
time ./dsa_example >/dev/null
0.42user 0.00system 0:00.42elapsed 100%CPU (0avgtext+0avgdata 3404maxresident)k
0inputs+0outputs (0major+153minor)pagefaults 0swaps

Running ./evp_example
time ./evp_example >/dev/null
0.12user 0.00system 0:00.12elapsed 99%CPU (0avgtext+0avgdata 3764maxresident)k
0inputs+0outputs (0major+157minor)pagefaults 0swaps

Это показывает, что использование ECDSA поверх NIST P-256 через EVP_DigestSign API в 2,66 раза быстрее, чем в исходном примере OP, и в 3,5 раза быстрее, чем исправленная версия.

В качестве позднего дополнительного примечаниякод в этом ответе также вычисляет дайджест SHA256 исходного текста, в то время как исходный код OP и «фиксированная» версия его пропускают.Поэтому ускорение, продемонстрированное вышеизложенными соотношениями, является еще более значительным!


TL; DR : Правильный способ эффективного использования цифровых подписей в OpenSSL - через API EVP_DigestSign: попытка использовать DSA_sign_setup() способом, предложенным выше, неэффективнав OpenSSL 1.1.0 и 1.1.1 и неправильно (как в , полностью нарушающем безопасность DSA и раскрывающим закрытый ключ ) в ≤1.0.2.Я полностью согласен с тем, что документация DSA API вводит в заблуждение и должна быть исправлена;к сожалению, функция DSA_sign_setup() не может быть полностью удалена, так как второстепенные выпуски должны сохранять двоичную совместимость, поэтому символ должен оставаться там даже в предстоящем выпуске 1.1.1 (но является хорошим кандидатом на удаление в следующем основном выпуске).

0 голосов
/ 08 июня 2018

Если сообщения большие, обычно их защищают и подписывают хэш.Это намного быстрее.Конечно, вам нужно передать сообщение, хэш и подпись, и процесс проверки должен включать как повторное хеширование и проверку на равенство, так и проверку цифровой подписи.

...