Редкий сбой с Crypto++ pbkdf2::DeriveKey()

Я использую библиотеку CryptoPP для хеширования некоторых паролей. Примерно в 1 из 10 случаев происходит сбой в строке DeriveKey ниже с segfault.

Даже при использовании фиксированных параметров сбои по-прежнему кажутся случайными. Мне интересно, может быть, мне нужен «\ 0» в конце моих строк. Или, может быть, буфер вывода должен быть инициализирован нулем или что-то в этом роде?

В любом случае, вот код.

#include <cryptopp/aes.h>
#include <cryptopp/algparam.h>
#include <cryptopp/filters.h>
#include <cryptopp/modes.h>
#include <cryptopp/sha.h>
#include <cryptopp/pwdbased.h>

int main()
{
CryptoPP::PKCS5_PBKDF2_HMAC<CryptoPP::SHA256> pbkdf2;

CryptoPP::byte salt[16];

CryptoPP::byte key[32];

/* Hard coded for testing purposes */
Common::podFromHex("00f8807a289655b2a8e38cda00182a32", salt);

/* Hard coded for testing purposes */
std::string password = "a";

std::cout << "Salt: " << Common::podToHex(salt) << std::endl;
std::cout << "Salt size: " << sizeof(salt) << std::endl;
std::cout << "Password: " << password.data() << std::endl;
std::cout << "Password size: " << password.size() << std::endl;

/* Rare segfault on this line */
pbkdf2.DeriveKey(
    key, sizeof(key), 0, (CryptoPP::byte *)password.data(),
    password.size(), salt, sizeof(salt), Constants::PBKDF2_ITERATIONS
);
}

Кажется, все инициализировано правильно - операторы печати каждый раз дают мне одно и то же:

Salt: 00f8807a289655b2a8e38cda00182a32
Salt size: 16
Password: a
Password size: 1

Кроме того, хешированный пароль можно использовать, когда он не segfault. Позже я использую шифрование AES, и я могу полностью расшифровать файл, и все данные соответствуют ожиданиям.

Кстати, исходный код для получения ключа можно найти здесь: https://www.cryptopp.com/docs/ref/pwdbased_8h_source.html#l00235

Спасибо.


person Zpalmtree    schedule 07.11.2018    source источник
comment
Нам нужно больше информации, например соответствующий исходный код для общей библиотеки. Точная строка сбоя с трассировкой стека также была бы полезна. Вероятно, вам следует запустить свою программу под Valgrind или другим инструментом проверки памяти, например Address Sanitizer.   -  person jww    schedule 08.11.2018
comment
Я думал о работе с valgrind - это немного больно, поскольку некоторый окружающий код, который я использую, выдает огромное количество ошибок - мне, вероятно, следует протестировать это в отдельной программе. Я избавлюсь от общей библиотеки для лучшего минимального примера - я почти уверен, что в этом разделе кода нет проблем, он просто преобразуется в/из шестнадцатеричного.   -  person Zpalmtree    schedule 08.11.2018
comment
Вы должны быть чисты от Valgrind в -O1. Если вы наблюдаете множество находок Valgrind в -O1, возможно, у вас есть некоторые проблемы, которые нужно исправить. См. также Краткое руководство по началу работы с Valgrind | Подготовка программы. ({Некоторые|многие} выводы в -O2 и -O3 являются ложноположительными).   -  person jww    schedule 08.11.2018
comment
О, конечно, есть масса проблем с текущей кодовой базой - Попытка исправить их медленно.   -  person Zpalmtree    schedule 08.11.2018


Ответы (2)


Рискну предположить, но salt не завершается NULL. Вероятно, программа обращается к 16-му элементу массива salt:

std::cout << "Salt: " << Common::podToHex(salt) << std::endl;

Следующее выполняется несколько раз без проблем. Это ваша программа за вычетом обращений к общей библиотеке.

Вызов std::memcpy занимает только крайние левые 16 байт строки. Он не делает преобразование. (Я только хотел убрать вызов Common).

$ cat test.cxx

#include "cryptlib.h"
#include "filters.h"
#include "sha.h"
#include "hex.h"
#include "files.h"
#include "pwdbased.h"

#include <string>
#include <iostream>
#include <cstring>

int main()
{
    using namespace CryptoPP;

    PKCS5_PBKDF2_HMAC<SHA256> pbkdf2;

    byte salt[16], key[32];

    /* Hard coded for testing purposes */
    // Common::podFromHex("00f8807a289655b2a8e38cda00182a32", salt);
    std::memcpy(salt, "00f8807a289655b2a8e38cda00182a32", 16);

    /* Hard coded for testing purposes */
    std::string password = "a";

    // std::cout << "Salt: " << Common::podToHex(salt) << std::endl;
    std::cout << "Salt: ";
    StringSource(salt, sizeof(salt), true, new HexEncoder(new FileSink(std::cout)));
    std::cout << std::endl;

    std::cout << "Salt size: " << sizeof(salt) << std::endl;
    std::cout << "Password: " << password.data() << std::endl;
    std::cout << "Password size: " << password.size() << std::endl;

    /* Rare segfault on this line */
    pbkdf2.DeriveKey(
        key, sizeof(key), 0, (byte *)password.data(),
        password.size(), salt, sizeof(salt), 10000 /*Constants::PBKDF2_ITERATIONS*/
    );

    std::cout << "Key: ";
    StringSource(key, sizeof(key), true, new HexEncoder(new FileSink(std::cout)));
    std::cout << std::endl;

    return 0;
}

Скомпилировано и выполнено с помощью:

$ g++ -DNDEBUG -g2 -O3 test.cxx -o test.exe ./libcryptopp.a
$ ./test.exe
Salt: 30306638383037613238393635356232
Salt size: 16
Password: a
Password size: 1
Key: F88BA6947B802C66F7E7A2BC0099AFD92C81DC293E3CC48C2DA3FA75E27ECE6B
person jww    schedule 08.11.2018
comment
Возможно, я неправильно понимаю, но разве в вашем примере соль не заканчивается нулем? Завтра я протестирую больше - я, вероятно, смогу избавиться от вызовов Common::, чтобы сделать лучший минимальный пример. - person Zpalmtree; 08.11.2018
comment
@Zpalmtree - правильно, но я кодирую только 16 байтов (что кодирует 32 символа), а затем вставляю 32 байта в std::cout. Я не полагаюсь на терминатор NULL. - person jww; 08.11.2018
comment
Возможно, я запутался, используя Common::, чтобы попытаться упростить пример. В реальном использовании я использовал метод для генерации 16 случайных байтов для соли. Кажется, я больше не могу воспроизвести ошибку, и valgrind не дает ничего полезного. Искушение свалить это на космические лучи, редкие сегфолеты очень расстраивают. :( - person Zpalmtree; 08.11.2018

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

Поскольку это занимало довольно много времени (с использованием 500 000 итераций), казалось, что здесь происходит сбой каждый раз, но на самом деле это было не из-за этой строки.

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

person Zpalmtree    schedule 13.11.2018