Как я могу безопасно хранить пароли моих пользователей?

169

Насколько это безопаснее, чем обычный MD5 ? Я только начал изучать безопасность пароля. Я довольно новичок в PHP.

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}
арматура
источник
Примечание. В php 5.4+ это встроено
Бенджамин Грюнбаум
Также см. Фреймворк хеширования паролей в PHP для PHP . Его портативный и защищенный от ряда распространенных атак на пароли пользователей.
jww
1
Обязательное «использовать PDO вместо строковой интерполяции» для людей, которые сегодня сталкиваются с этим вопросом.
Фонд Моники судебный процесс

Ответы:

270

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

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


Новый API паролей PHP (5.5.0+)

Если вы используете PHP версии 5.5.0 или новее, вы можете использовать новый упрощенный API хеширования паролей

Пример кода с использованием API паролей PHP:

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(Если вы все еще используете устаревшую версию 5.3.7 или новее, вы можете установить ircmaxell / password_compat, чтобы иметь доступ к встроенным функциям)


Улучшение соленых хэшей: добавить перец

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

Существует простой класс, который безопасно реализует этот шаблон, я рекомендую: Netsilik / PepperedPasswords ( github ).
Он поставляется с лицензией MIT, поэтому вы можете использовать его по своему усмотрению, даже в проприетарных проектах.

Пример использования кода Netsilik/PepperedPasswords:

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


Старая стандартная библиотека

Обратите внимание: вам это больше не нужно! Это только для исторических целей.

Взгляните на: Переносимая среда хеширования паролей PHP : phpass и убедитесь, что вы используете CRYPT_BLOWFISHалгоритм, если это возможно.

Пример кода с использованием phpass (v0.2):

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPass был реализован в некоторых довольно известных проектах:

  • phpBB3
  • WordPress 2.5+, а также bbPress
  • релиз Drupal 7, (модуль доступен для Drupal 5 и 6)
  • другие

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

Для получения дополнительной информации о схемах хранения паролей, прочитайте сообщение в блоге Джеффа : « Вы, вероятно, храните пароли неправильно»

Что бы вы ни делали, если вы идете на подход « Я сделаю это сам, спасибо », не используйте MD5или SHA1больше . Это хороший алгоритм хэширования, но он считается нарушенным в целях безопасности .

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

Jacco
источник
29

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

Антон Гоголев
источник
1
В Nettuts + есть хорошая статья о безопасности в PHP, также упоминается подмена пароля. Может быть, вы должны взглянуть на: net.tutsplus.com/tutorials/php/…
Фабио Антунес
3
Nettuts + - очень плохая статья для использования в качестве модели - она ​​включает в себя использование MD5, который можно очень легко переманивать даже солью. Вместо этого просто используйте библиотеку PHPass, которая намного, намного лучше, чем любой код, который вы можете найти на учебном сайте, то есть этот ответ: stackoverflow.com/questions/1581610/…
RichVel
11

Лучшим способом было бы для каждого пользователя иметь уникальную соль.

Преимущество наличия соли заключается в том, что злоумышленнику становится сложнее предварительно сгенерировать подпись MD5 для каждого словарного слова. Но если злоумышленник узнает, что у вас есть фиксированная соль, он может предварительно сгенерировать подпись MD5 для каждого словарного слова с префиксом вашей фиксированной соли.

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

R Самуэль Клатчко
источник
3
Соли обычно хранятся вместе с хешем пароля (например, выводом crypt()функции). И так как вам все равно придется извлекать хэш пароля, использование соли для конкретного пользователя не сделает процедуру более дорогой. (Или вы имели в виду создание новой случайной соли дорого? Я так не думаю.) В противном случае +1.
Иншалла
В целях безопасности вы можете захотеть предоставить доступ к таблице только с помощью хранимых процедур и предотвратить возвращение хэша. Вместо этого клиент передает то, что он считает хешем, и получает флаг успеха или неудачи. Это позволяет сохраненному процессу регистрировать попытку, создавать сеанс и т. Д.
Стивен Судит
@Inshallah - если у всех пользователей одинаковая соль, вы можете повторно использовать атаку по словарю, которую вы используете на user1 против user2. Но если у каждого пользователя есть уникальная соль, вам нужно будет создать новый словарь для каждого пользователя, которого вы хотите атаковать.
R Самуэль Клатчко
@R Самуил - именно поэтому я проголосовал за твой ответ, потому что он рекомендует стратегию наилучшей практики, чтобы избежать таких атак. Мой комментарий должен был выразить мое недоумение по поводу того, что вы сказали о дополнительной стоимости соли для каждого пользователя, которую я совсем не понимал. (поскольку «соли обычно хранятся вместе с хэшем пароля», любые дополнительные требования к памяти и процессору для соли для каждого пользователя настолько микроскопичны, что их даже не нужно упоминать ...)
Иншалла,
@Inshallah - я подумал о случае, когда у вас проверяется база данных, если хешированный пароль в порядке (тогда у вас есть один дБ для получения соли и второй доступ к БД для проверки хэшированного пароля). Вы правы в том случае, когда вы загружаете солт / хешированный пароль за один раз, а затем проводите сравнение на клиенте. Извините за путаницу.
R Самуэль Клатчко
11

С PHP 5.5 (то, что я описываю, доступно даже для более ранних версий, см. Ниже), я хотел бы предложить использовать его новое встроенное решение: password_hash()и password_verify(). Он предоставляет несколько опций для достижения необходимого уровня защиты паролем (например, путем указания параметра «стоимость» через $optionsмассив)

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

вернется

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

Как вы можете видеть, строка содержит соль, а также стоимость, которая была указана в опциях. Он также содержит используемый алгоритм.

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

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

Проверка работает так:

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

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

Существует небольшая библиотека (один файл PHP), которая даст вам PHP 5.5 password_hashв PHP 5.3.7+: https://github.com/ircmaxell/password_compat

akirk
источник
2
В большинстве случаев лучше не указывать параметр соли. Функция создает соль из случайного источника операционной системы, поэтому очень мало шансов, что вы сможете обеспечить лучшую соль самостоятельно.
martinstoeckli
1
Это то, что я написал, не так ли? «если соль не указана, она генерируется случайным образом, поэтому желательно не указывать соль»
akirk
В большинстве примеров показано, как добавить оба параметра, даже если не рекомендуется добавлять соль, поэтому мне интересно, почему? И если честно, я читаю только комментарий за кодом, а не следующую строку. В любом случае, не было бы лучше, если в примере показано, как лучше всего использовать эту функцию?
martinstoeckli
Вы правы, я согласен. Я соответственно изменил свой ответ и прокомментировал строку. Спасибо
akirk
Как я должен проверить, совпадают ли сохраненный пароль и введенный пароль? Я использую password_hash()и password_verifyнезависимо от того, какой пароль (правильный или нет) я использовал, я
получаю
0

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

Вы также можете взглянуть на SHA-1, который, похоже, становится все более популярным в наши дни.

nickf
источник
6
Примечание внизу поста г-на Этвуда (красного цвета) связано с другим постом практикующего специалиста по безопасности, в котором говорится, что использование MD5, SHA1 и других быстрых хэшей для хранения паролей является очень неправильным.
sipwiz
2
@ Мэтью Шарли: Я не согласен с тем, что дополнительные усилия, накладываемые дорогостоящими алгоритмами хеширования паролей, являются ложной безопасностью. Это для защиты от перебора легко угадываемых паролей. Если вы ограничиваете попытки входа в систему, то вы защищаете от того же самого (хотя и более эффективно). Но если у злоумышленника есть доступ к хешам, хранящимся в БД, он сможет довольно быстро взломать такие (легко угадываемые) пароли (в зависимости от того, насколько легко угадать). По умолчанию для алгоритма шифрования SHA-256 используется округление 10000, что усложнит его в 10000 раз.
Иншалла
3
Медленные хеши на самом деле создаются путем итерации быстрого очень большого числа раз и перестановки данных между каждой итерацией. Цель состоит в том, чтобы гарантировать, что даже если злоумышленник получит копию хэшей вашего пароля, ему потребуется значительное количество процессорного времени, чтобы проверить свой словарь на соответствие вашим хешам.
Кафе
4
@caf: Я полагаю, что алгоритм bcrypt использует параметризуемую дороговизну планирования ключей Eksblowfish; не совсем уверен, как это работает, но планирование ключей часто является очень дорогой операцией, выполняемой во время инициализации объекта контекста шифрования, до того, как будет выполнено любое шифрование.
Иншалла
3
Inshallah: Это правда - алгоритм bcrypt представляет собой другой дизайн, в котором базовый криптопримитив является блочным шифром, а не хеш-функцией. Я имел в виду схемы, основанные на хеш-функциях, например, crypt () в PHK.
Кафе
0

Я хочу добавить:

  • Не ограничивайте пароли пользователей длиной

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

  • Не отправляйте пароли пользователей по электронной почте

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

  • Обновите хеши паролей пользователей

Хеш пароля может быть устаревшим (параметры алгоритма могут обновляться). С помощью функции password_needs_rehash()вы можете проверить это.


источник