Как сгенерировать псевдослучайную 32-байтовую строку для использования в качестве соли в криптографической хеш-функции?

Я пытаюсь закодировать функцию шифрования паролей на С++ (Примечание: только в образовательных целях. На самом деле я не буду хранить с ней свои пароли.), но я не уверен, как создать случайную 32-байтовую соль, используя предопределенный набор символов . Как бы я это сделал?

#include <random>
#include <iostream>

using namespace std;


void genSalt() {

    const char charset[] = {

        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "!£$%^&*():@~#'?/><.,|`¬¦"

    };

}

Код должен генерировать случайную 32-байтовую строку на основе определенного набора символов 'charset'. Я не уверен, как этого добиться.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
1 989
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если вы можете использовать C++11, вы можете использовать random_shuffle для перетасовки массива элементов, а затем всегда возвращать первые 32 элементы.

Что-то вроде следующего должно работать для вашего случая:

void genSalt() {
   static char charset[] = {
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!£$%^&*():@~#'?/><.,|`¬¦" };

    random_shuffle(begin(charset), end(charset));

    for(int i = 0 ; i < 32; i++)
       cout<<charset[i];
    cout<<endl;
}

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


Другой подход заключается в случайном выборе элементов из последовательности один за другим, как показано ниже:

void genSalt() {
    static const char charset[] = {
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!£$%^&*():@~#'?/><.,|`¬¦" };


    for (int i = 0; i < 32; ++i) {
        cout<<charset[rand() % (sizeof(charset) - 1)];
    }
}

Примечание. std::random_shuffle устарело в С++ 14, удалено из С++ 17. Используйте std::shuffle (доступно начиная с С++ 11) и прилично подготовленный prng.

WhozCraig 29.05.2019 13:07

@WhozCraig Хороший вопрос. Но общая идея остается в силе. Спасибо

Davide Spataro 29.05.2019 14:12
Ответ принят как подходящий

Первое, на что вы должны обратить внимание, это хранение некоторых из этих символов в исходном файле. В зависимости от кодировки, £, ¬ или ¦ потенциально могут состоять из более чем одного байта, что может испортить ваш результат; вы должны хранить их как жестко закодированные байты, если хотите, чтобы все шло гладко независимо от кодировки:

const char charset[] =
    "0123456789"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "!\xA3$%^&*():@~#'?/><.,|`\xAC\xA6";

// \xA3 = single-byte extended encoding for £
// \xAC = ¬
// \xA6 = ¦

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

Что касается генерации соли пароля, я не криптолог, но мне кажется немного сомнительным использовать что-либо, кроме криптографически безопасного генератора псевдослучайных чисел. Если это просто упражнение на C++, встроенные генераторы случайных чисел будут в порядке. На самом деле я никогда не использовал функции PRNG С++ 11, так что это тоже полезное упражнение для меня.

Вы начинаете с создания random_device, а затем движка рандомизатора:

#include <random>
std::random_device my_random_device;
std::default_random_engine my_random_engine(my_random_device());

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

random_device обрабатывает заполнение автоматически, тогда как, если вы используете старую функцию rand в стиле C, вам нужно вызвать srand с начальным значением (например, системным временем), чтобы инициализировать его перед созданием чего-либо.

Чтобы выбрать персонажей из вашего источника соли, вы выбираете метод распространения, о котором вы можете получить более подробную информацию здесь: https://en.cppreference.com/w/cpp/numeric/random

В этом случае вам нужно равномерное распределение по набору солевых символов:

std::uniform_int_distribution<int> random_number(0, sizeof(charset) - 1);

Который вы можете вызвать как random_number(my_random_engine), чтобы получить число между 0 и индексом последнего символа (не забудьте минус 1, чтобы пропустить нулевой терминатор).

И тогда с этим легко сэмплировать символы и строить строку:

std::string salt;
salt.reserve( 32 );
for( int i = 0; i < 32; i++ ) {
    salt.push_back(charset[random_number(my_random_engine)]);
}
std::cout << "salt result: " << salt << std::endl;

Рабочий пример: https://wandbox.org/permlink/mGd8pYP9Y3injuuG


Еще одна вещь, которую я хотел бы упомянуть, это распространенная ошибка использования % против случайного числа. Например, рассмотрим этот тестовый пример с использованием старой функции rand() в стиле C:

int main() {
    // Seed randomizer
    srand( time(0) );

    // Print a random number between 0 and 1999
    int number = rand() % 2000;
    std::cout << number;
}

Обычно людям все равно, потому что это «достаточно случайно», но вы не получите равномерного распределения по модулю (%). rand() генерирует число между 0 и RAND_MAX, и вместо этого вы должны масштабировать возвращаемый диапазон, чтобы он соответствовал желаемому диапазону.

// Sample random number between 0 and 1999. (add 1 to rand max to make 2000 not slightly possible)
int number = rand() * 2000 / (RAND_MAX+1)

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

Другие вопросы по теме