Детерминированная генерация случайного пароля
Я написал несколько программ на C++ для генерации детерминированных случайных паролей на основе пользовательского ввода. Пользователи предоставят предложение и слово, и эти строки будут использованы в качестве входных данных для PBKDF2. Затем байты с выхода PBKDF2 будут использоваться для случайного выбора символов для пароля.
Вот пример вывода:
./a.out "ned was here." facebook
Bit_strength: 43.5-bits.
Password_length: 8
uT3=de+D
./a.out "ned was here." "stack overflow"
Bit_strength: 69.5-bits.
Password_length: 12
qO0+Zqp<X<`W
./a.out "ned was here." beer
Bit_strength: 199.3-bits.
Password_length: 32
sV2;MMD|=swA&M`mVk1v{M:3\\<_*<tl
Обновление: вот модифицированный пример исходного кода. Он удаляет Mersenne Twister (спасибо всем за замечания по этому поводу) и использует только байты из PBKDF2 для случайных чисел. Я написал это быстро. Есть ли что-то очевидное, что я пропустил или, может быть, ввел некоторые новые проблемы? Я удалил части моего исходного вопроса о (RNG, выбор набора символов, пример исходного кода и т. Д.), Которые больше не применяются. Спасибо всем за ваши комментарии и ответы.
std::string deterministic_password( const std::string& sentence, const std::string& word, boost::uint8_t pass_length )
{
bool have_lower = false;
bool have_upper = false;
bool have_number = false;
bool have_special = false;
bool requirements_met = false;
std::string password;
if ( pass_length > 64 )
pass_length = 64;
// Our character sets
const std::string lower_chars( "abcdefghijkmnpqrstuvwxyz" ); // no l or o
const std::string upper_chars( "ABCDEFGHJKLMNPQRSTUVWXYZ" ); // no I or O
const std::string number_chars( "23456789" ); // no 1 or 0
const std::string special_chars( "~!#%^&_[]{}()<>;:,.?" );
const std::string all_chars( lower_chars + upper_chars + number_chars + special_chars );
// The maps that we'll pick chars from
std::map<boost::uint8_t, char> lower_map, upper_map, number_map, special_map, all_map;;
// Populate the maps
for ( boost::uint8_t i = 0; i != lower_chars.size(); ++i )
{
lower_map[i] = lower_chars[i];
}
for ( boost::uint8_t i = 0; i != upper_chars.size(); ++i )
{
upper_map[i] = upper_chars[i];
}
for ( boost::uint8_t i = 0; i != number_chars.size(); ++i )
{
number_map[i] = number_chars[i];
}
for ( boost::uint8_t i = 0; i != special_chars.size(); ++i )
{
special_map[i] = special_chars[i];
}
for ( boost::uint8_t i = 0; i != all_chars.size(); ++i )
{
all_map[i] = all_chars[i];
}
std::map<boost::uint8_t, char>::const_iterator lower_it = lower_map.begin();
std::map<boost::uint8_t, char>::const_iterator upper_it = upper_map.begin();
std::map<boost::uint8_t, char>::const_iterator number_it = number_map.begin();
std::map<boost::uint8_t, char>::const_iterator special_it = special_map.begin();
std::map<boost::uint8_t, char>::const_iterator all_it = all_map.begin();
char seed_bits[512];
CryptoPP::PKCS5_PBKDF2_HMAC<CryptoPP::SHA1> pbkdf;
pbkdf.DeriveKey(
// buffer that holds the derived key
(byte *)seed_bits,
// Length of derived key in bytes
512,
// purpose byte. unused by this PBKDF implementation.
0,
// sentence bytes (User provided)
(const byte *)sentence.data(),
sentence.size(),
// word bytes (User provided. This is salt)
(const byte *)word.data(),
word.size(),
// iteration count
32768,
0 );
double bit_strength = 0;
for( boost::uint16_t i = 0; i != 512; ++i )
{
if ( have_lower and have_upper and have_number and have_special )
{
requirements_met = true;
}
if ( requirements_met and (password.size() == pass_length) )
{
break;
}
boost::uint8_t number = seed_bits[i];
lower_it = lower_map.find(number);
upper_it = upper_map.find(number);
number_it = number_map.find(number);
special_it = special_map.find(number);
all_it = all_map.find(number);
if ( lower_it != lower_map.end() and !have_lower )
{
//std::cout << (boost::uint16_t)lower_it->first << " " << lower_it->second << "\n";
password.push_back(lower_it->second);
have_lower = true;
bit_strength += log(lower_map.size())/log(2);
continue;
}
if ( upper_it != upper_map.end() and !have_upper )
{
//std::cout << (boost::uint16_t)upper_it->first << " " << upper_it->second << "\n";
password.push_back(upper_it->second);
have_upper = true;
bit_strength += log(upper_map.size())/log(2);
continue;
}
if ( number_it != number_map.end() and !have_number )
{
//std::cout << (boost::uint16_t)number_it->first << " " << number_it->second << "\n";
password.push_back(number_it->second);
have_number = true;
bit_strength += log(number_map.size())/log(2);
continue;
}
if ( special_it != special_map.end() and !have_special )
{
//std::cout << (boost::uint16_t)special_it->first << " " << special_it->second << "\n";
password.push_back(special_it->second);
have_special = true;
bit_strength += log(special_map.size())/log(2);
continue;
}
if ( all_it != all_map.end() and requirements_met and (password.size() != pass_length) )
{
//std::cout << (boost::uint16_t)all_it->first << " " << all_it->second << "\n";
password.push_back(all_it->second);
bit_strength += log(all_map.size())/log(2);
continue;
}
}
std::cout.precision(1);
std::cout.imbue(std::locale(""));
std::cout << std::fixed << "Bit_strength: " << bit_strength << "-bits." << "\n";
std::cout << "Password_length: " << password.size() << "\n";
std::cout << password << "\n";
std::cout << "\n";
return password;
}
1 ответ
Это небезопасно из-за использования Mersenne Twister RNG согласно комментарию Ника Джонсона.
Если вам нужны недетерминированные пароли, вы можете использовать random_device, который криптографически безопасен, но тогда люди не получат один и тот же пароль для одного и того же предложения. В противном случае вы не получите безопасный входной источник для iform__int_disribution, потому что те, которые предназначены для безопасного использования, являются "недетерминированными". Безопасный детерминированный rng - это действительно функция распределения ключей, такая как pbkdf2 или потоковый шифр. Насколько я могу судить, ни один из них не работает как вход для unifrom_int_distribution.
Вы должны получить случайные байты самостоятельно из вывода pbkdf2. Но будьте очень осторожны, потому что вы можете легко облажаться. То, что я описал ниже, например, не работает. Поскольку вы получаете 256 возможных значений из одного байта, их сопоставление с 92 символами пароля приводит к появлению за кадром. 256-92*2 оставляет вам 72. Это означает, что первые 72 символа будут представлены более. Чтобы это исправить, вам действительно нужно продолжать запрашивать случайные байты до тех пор, пока вы не получите один в диапазоне, который вам действительно нужен, так что вы можете использовать множество случайных байтов для получения равномерного распределения.
Так что вам нужно взять исходный вывод из pbkdf2 и преобразовать его в пароль. Спросите pbkdf2 для 160 бит данных. Затем возьмите каждый байт, приведите его к uint8, возьмите в нем длину вашего массива символов (предполагается, что ваш массив символов меньше максимального значения единицы 8) и используйте его в качестве индекса для выбора символа. Предполагая, что pbkdf2 является случайным, и приведение просто берет необработанные биты и, следовательно, дает вам равномерно число от 0 до 2^8-1, все в порядке.
Мерсенн Твистер ненужный
Нет никакого смысла в использовании Mersenne Twister. PBKDF2 является PRNG. Обычно он используется для получения ключей, когда требуется какой-то случайный криптографический ключ из некоторого входного текста, и поэтому его вывод будет более чем случайным. Это значительно упрощает получение равномерного распределения, поэтому, вероятно, вы и сделали это.
Мерсенн Твистер не является безопасным
Согласно статье в Википедии:
Алгоритм в исходном виде не подходит для криптографии (в отличие от Blum Blum Shub). Наблюдение за достаточным количеством итераций (624 в случае MT19937, так как эта цифра является размером вектора состояния, из которого создаются будущие итерации), позволяет прогнозировать все будущие итерации
Это на самом деле нарушает вашу безопасность, так как запуск такой детерминированной функции на случайных данных делает ее не случайной
Повысьте равномерное случайное искажение
Действительно ли crypboost::random::iform_int_distribution возвращает равномерное распределение, а не усечение / округление?, Документация, кажется, указывает, что это работает правильно, но это кажется непреднамеренным для криптографического использования, и документация не является явной, так что кто знает. Просто для того, чтобы указать, что случайность в криптографическом контексте означает нечто иное в математическом контексте, поскольку криптографические rng действительно случайны, а те, которые, скажем, в matlab или mathematica, по-видимому, имеют некоторые фиксированные статистические свойства. Я не могу понять, как у формы были бы такие значения, но опять же я не понимаю, насколько случайные имеют такие значения.
Renterpret бросок
Последний вопрос заключается в том, изменяет ли приведенная интерпретация данные из PBKDF2 (т. Е. Обнуляет некоторые биты). Я не думаю, что это так, но вывод значения вызова в массив байтов со всеми 0 и затем со всеми 1 должен сказать вам наверняка. В первом случае вы получаете 0, а во втором вы хотите 2^64 -1.
Кстати, использование специальных символов делает пароли менее случайными (но с меньшей вероятностью попадет в простой список паролей).
Обратите внимание, что, как я узнал при использовании случайных паролей lastpass, многие сайты имеют ограничения по длине, сложности и требования. Убедитесь, что вы это разрешаете и при этом получаете равномерное распределение