Как создать хорошую соль — достаточно ли безопасна моя функция?

Вот функция, которую я использую для генерации случайных солей:

function generateRandomString($nbLetters){
    $randString="";
    $charUniverse="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    for($i=0; $i<$nbLetters; $i++){
       $randInt=rand(0,61);
        $randChar=$charUniverse[$randInt];
        $randString=$randomString.$randChar;
    }
    return $randomString;
}

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

Это уместно? Должен ли я использовать большее подмножество символов, и если да, то есть ли простой способ сделать это в PHP?


person JDelage    schedule 04.11.2010    source источник


Ответы (11)


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

Вы должны всегда использовать встроенные функции для хеширования/проверки паролей. Использование собственных алгоритмов в любой момент связано с огромным ненужным риском.

Для PHP рассмотрите возможность использования password_hash() с алгоритмом PASSWORD_BCRYPT . Нет необходимости предоставлять свою собственную соль.

Ниже мой оригинальный ответ для потомков:


Предупреждение. Следующая реализация не создает непредсказуемую соль, согласно документации для уникальный идентификатор.

На странице php sha1:

$salt = uniqid(mt_rand(), true);

Это выглядит проще и эффективнее (поскольку каждый уникален), чем то, что вы предложили.

person fredley    schedule 04.11.2010

Если вы работаете в Linux, /dev/urandom, вероятно, ваш лучший источник случайности. Он предоставляется самой ОС, поэтому он гарантированно будет намного надежнее любой встроенной функции PHP.

$fp = fopen('/dev/urandom', 'r');
$randomString = fread($fp, 32);
fclose($fp);

Это даст вам 32 байта случайного блоба. Вы, вероятно, захотите передать это через что-то вроде base64_encode(), чтобы сделать его разборчивым. Не нужно жонглировать персонажами самостоятельно.

Изменить 2014 г. В PHP 5.3 и более поздних версиях openssl_random_pseudo_bytes() — это самый простой способ получить группу случайных байтов. В системах *nix он использует /dev/urandom за кулисами. В системах Windows используется другой алгоритм, встроенный в библиотеку OpenSSL.

Связано: https://security.stackexchange.com/questions/26206

Связано: должен ли я использовать urandom или openssl_random_pseudo_bytes?

person kijin    schedule 04.11.2010
comment
Я согласен с этим. OP использует некриптографический безопасный генератор псевдослучайных чисел. Я бы только добавил, что соль может храниться как двоичный файл в БД, чтобы уменьшить задержку кодирования/декодирования base64, поскольку хэш должен быть прозрачным для входа в систему аутентификации. Кроме того, если сервер IIS/Win32, функция mcrypt_create_iv(), если она доступна, будет объединять криптоAPI win32, если вам нужны хорошие случайные биты в этом сценарии и вы не хотите иметь дело с COM-интерфейсом (CAPICOM или что-то еще). Поскольку ОП спросил о случайной соли, я думаю, что ответ @kijin - лучшее решение. - person DrPerdix; 04.11.2010
comment
+1 это лучший ответ на этой странице, хотя я бы не стал использовать base64_encode. Имейте в виду, что все функции дайджеста сообщений являются двоично-безопасными, и вам нужна полная байтовая соль, буквенно-цифровые радужные таблицы очень распространены, но радужные таблицы base256 неслыханны. - person rook; 08.11.2010
comment
@Rook Конечно, вы можете передать любую двоичную строку хеш-функции. Но соль обычно тоже хранят вместе с гашишем. (Как еще вы потом собираетесь сравнивать хэш с паролем?) Видите ли, большинство людей склонны хранить все это в текстовом столбце в базе данных, такой как MySQL. Текстовые столбцы не принимают произвольный двоичный код, поэтому вы должны либо превратить его в столбец больших двоичных объектов, или использовать что-то вроде base64. Это просто делает вашу соль длиннее; криптостойкость одинакова. - person kijin; 08.11.2010
comment
@kijin я понимаю, что ты говоришь. Но на самом деле, если вы экранируете двоичный файл перед вставкой, он сохраняет значение. Вы даже можете хранить изображения в базе данных, mysql_real_escape_string(). - person rook; 08.11.2010
comment
@Rook Даже если вы избегаете этого, blob является правильным типом данных для двоичных значений, таких как изображения. Текстовые столбцы имеют прикрепленный к ним набор символов, который может испортить ваш двоичный файл в зависимости от того, какую СУБД вы используете. Я хотел дать ответ, который хорошо сочетается с типами varchar/text, которые обычно назначаются полям пароля, поэтому я придерживаюсь своей рекомендации, что любая двоичная соль должна быть закодирована в base64. Если вы хотите использовать большой двоичный объект для экономии места на диске, это нормально. - person kijin; 08.11.2010
comment
@kijin Как лучший ответчик по безопасности я говорю, что самый безопасный ответ — самый важный. Буквенно-цифровая таблица, достаточно большая, чтобы сломать соль base64, также сломает большие хешированные пароли, которые не содержат соли. При этом, что более вероятно, радужная таблица, состоящая из +256 байт или охватывающая всю базу 256... И то, и другое очень маловероятно, поэтому у меня будет соль из базы 256 и 256 байт, так что даже у самого могущественного правительства будет достаточно дискового пространства для хранения такой таблицы. - person rook; 08.11.2010
comment
@Rook base64 уменьшает количество случайностей на байт, но также увеличивает общее количество байтов. Чистый эффект не влияет на безопасность. В моем примере выше я использовал 256 бит (32 байта) случайности. Кодировка base64 сделает его длиной 44 байта, но количество случайностей по-прежнему составляет 256 бит. Вам понадобятся все атомы во Вселенной и еще несколько, чтобы построить достаточно большое запоминающее устройство, чтобы хранить все возможные комбинации из 44 буквенно-цифровых символов. Посчитай, увидишь. - person kijin; 08.11.2010

password_hash() доступен в PHP 5.5 и новее. Я удивлен, узнав, что это не упоминается здесь.

С помощью password_hash() нет необходимости генерировать соль, поскольку соль генерируется автоматически с использованием алгоритма bcrypt, и поэтому нет необходимости составлять набор символов.

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

Как работает хеширование пароля():

Функция password_hash() выводит уникальный хэш пароля при сохранении строки в базе данных — рекомендуется, чтобы столбец содержал до 255 символов.

$password = "goat";
echo password_hash($password, PASSWORD_DEFAULT);
echo password_hash($password, PASSWORD_DEFAULT);
echo password_hash($password, PASSWORD_DEFAULT);

// Output example (store this in the database)
$2y$10$GBIQaf6gEeU9im8RTKhIgOZ5q5haDA.A5GzocSr5CR.sU8OUsCUwq  <- This hash changes.
$2y$10$7.y.lLyEHKfpxTRnT4HmweDKWojTLo1Ra0hXXlAC4ra1pfneAbj0K
$2y$10$5m8sFNEpJLBfMt/3A0BI5uH4CKep2hiNI1/BnDIG0PpLXpQzIHG8y 

Чтобы проверить хешированный пароль, используйте password_verify():

$password_enc = password_hash("goat", PASSWORD_DEFAULT);
dump(password_verify('goat', $password_enc)); // TRUE
dump(password_verify('fish', $password_enc)); // FALSE

При желании соль можно добавить вручную, как вариант, например так:

$password = 'MyPassword';
$salt = 'MySaltThatUsesALongAndImpossibleToRememberSentence+NumbersSuch@7913';
$hash = password_hash($password, PASSWORD_DEFAULT, ['salt'=>$salt]);
// Output: $2y$10$TXlTYWx0VGhhdFVzZXNBT.ApoIjIiwyhEvKC9Ok5qzVcSal7T8CTu  <- This password hash not change.
person Kristoffer Bohmann    schedule 23.10.2015
comment
Этот ответ хорош, потому что он охватывает новейшую реализацию PHP для генерации паролей. Но это не отвечает на вопрос, как создать хорошую соль (иногда вам это явно нужно, например, для других целей хеширования). - person Gottlieb Notschnabel; 29.10.2015
comment
Спасибо. Создать новую соль очень просто: $salt = password_hash("goat", PASSWORD_DEFAULT); Ответ обновлен. - person Kristoffer Bohmann; 30.10.2015
comment
@KristofferBohmann - Таким образом вы не создаете соль, результатом является хэш пароля. Было бы очень расточительно для процессора генерировать соли таким образом, а соли не оптимальны. - person martinstoeckli; 30.10.2015

Замените rand(0,61) на mt_rand(0, 61), и все будет в порядке (так как mt_rand лучше производит случайные числа)...

Но более важным, чем крепость соли, является то, как вы ее измельчаете. Если у вас есть отличная солевая программа, но вы делаете только md5($pass.$salt), вы выбрасываете соль. Лично я рекомендую растянуть хэш... Например:

function getSaltedHash($password, $salt) {
    $hash = $password . $salt;
    for ($i = 0; $i < 50; $i++) {
        $hash = hash('sha512', $password . $hash . $salt);
    }
    return $hash;
}

Для получения дополнительной информации о растяжении хеша ознакомьтесь с этим SO-ответом...

person ircmaxell    schedule 04.11.2010
comment
Моя функция: function stringHashing($saltedString){$hashedString=hash('sha256',$saltedString); вернуть $хешированнуюстроку; } - person JDelage; 05.11.2010
comment
@ircmaxell Это нужно обновить или это все еще актуально сейчас? - person JayIsTooCommon; 06.11.2017

Я бы последовал совету из другого ответа и использовал mt_rand(0, 61), потому что Вихрь Мерсенна производит лучшую энтропию.

Кроме того, ваша функция на самом деле состоит из двух частей: генерация случайных $nbLetters цифр и кодирование их в base62. Это сделает вещи более понятными для программиста сопровождения (возможно, для вас!), который наткнется на него через несколько лет:

// In a class somewhere
private $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

private function getBase62Char($num) {
    return $chars[$num];
}

public function generateRandomString($nbLetters){
    $randString="";

    for($i=0; $i < $nbLetters; $i++){
        $randChar = getBase62Char(mt_rand(0,61));
        $randString .= $randChar;
    }

    return $randomString;
}
person Drew Stephens    schedule 04.11.2010

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

<?php
function generateRandomString($length = 10) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, strlen($characters) - 1)];
    }
    return $randomString;
}

function get_true_random_number($min = 1, $max = 100) {
    $max = ((int) $max >= 1) ? (int) $max : 100;
    $min = ((int) $min < $max) ? (int) $min : 1;
    $options = array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADER => false,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_ENCODING => '',
        CURLOPT_USERAGENT => 'PHP',
        CURLOPT_AUTOREFERER => true,
        CURLOPT_CONNECTTIMEOUT => 120,
        CURLOPT_TIMEOUT => 120,
        CURLOPT_MAXREDIRS => 10,
    );

    $ch = curl_init('http://www.random.org/integers/?num=1&min='
        . $min . '&max=' . $max . '&col=1&base=10&format=plain&rnd=new');
    curl_setopt_array($ch, $options);
    $content = curl_exec($ch);
    curl_close($ch);

    if(is_numeric($content)) {
        return trim($content);
    } else {
        return rand(-10,127);
    }
}

function generateSalt() {
    $string = generateRandomString(10);
    $int = get_true_random_number(-2,123);
    $shuffled_mixture = str_shuffle(Time().$int.$string);
    return $salt = md5($shuffled_mixture);
}

echo generateSalt();
?>

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

person Community    schedule 08.04.2014

Вот гораздо лучший способ, если у вас есть окна и вы не можете сделать /dev/random.

//Key generator
$salt = base64_encode(openssl_random_pseudo_bytes(128, $secure));
//The variable $secure is given by openssl_random_ps... and it will give a true or false if its tru then it means that the salt is secure for cryptologic.
while(!$secure){
    $salt = base64_encode(openssl_random_pseudo_bytes(128, $secure));
}
person tor    schedule 30.12.2014

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

Вам не нужно ничего генерировать и не нужно хранить дополнительные данные.

person NikiC    schedule 04.11.2010
comment
Неплохая идея, но вы, вероятно, захотите сначала выполнить тривиальную функцию для имени пользователя. может быть таким же простым, как повторение его дважды или добавление символа в конце. хотя то, что вы предлагаете, является математически обоснованным, об этом можно легко догадаться. - person jon_darkstar; 04.11.2010
comment
Кого волнует, можно ли это угадать? Целью соли является предотвращение использования одной радужной таблицы для всех хэшей в БД. И это делает свою работу. - person NikiC; 04.11.2010
comment
Да, это так. Соль может быть даже общедоступной, важно то, что соль индивидуальна для каждого пароля. - person Gottlieb Notschnabel; 29.10.2015

Достаточно простая техника:

$a = array('a', 'b', ...., 'A', 'B', ..., '9');
shuffle($a);
$salt = substr(implode($a), 0, 2);  // or whatever sized salt is wanted

В отличие от uniqid() он генерирует случайный результат.

person Jay    schedule 30.05.2013

Я использую это:

$salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM));
person William Wallace    schedule 27.02.2014
comment
Кодировка Base64 также может возвращать символы «+» и «/», возможно, это не то, что нужно OP. Затем соль должна быть создана для конкретного алгоритма с заданной длиной, длина в вашем примере фиксированная и ее трудно предсказать. - person martinstoeckli; 27.02.2014

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

Во-вторых, вы должны расширить $charUniverse с помощью некоторых специальных символов, таких как @,!#- и т. д.

person SaidbakR    schedule 06.04.2014