Одной из главных претензий было отсутствие в статье упоминания
password_hash
, как мы и обещали, вторую часть данного материала начнем как раз таки с хеширования пароля с помощью password_hash
. Также напоминаем о том, что написание данной статьи было навеяно запуском новой группы по курсу «Backend-разработчик на PHP», но к программе обучения данный материал отношения не имеет. Подробнее о программе обучения можно будет узнать на дне открытых дверей, а на примере бесплатного вебинара по теме «ServerLess PHP», вы можете оценить формат проведения лекций.
Пожалуй на этом мы завершим и без того затянувшееся предисловие и перейдем непосредственно к статье.
Хеширование пароля с помощью password_hash
Эта функция создает хеш пароля в соответствии с теми параметрами, которые мы ей зададим. Она использует односторонний алгоритм.
Мы можем выбрать, какой тип алгоритма использовать, задав одну из констант по своему выбору:
PASSWORD_DEFAULT
из PHP 5.5 использует Bcrypt в качестве алгоритма по умолчанию. Однако с течением времени это изменяется по мере обнаружения новых, более безопасных алгоритмов либо иных факторов.PASSWORD_BCRYPT
создает хешcrypt()
. Обычно он содержит 60 символов, опознать его можно по идентификатору в формате "$2y$".PASSWORD-ARGON2I
Argon2 на данный момент является одним из наиболее безопасных алгоритмов хеширования. Он доступен только в том случае, если РНР был скомпилирован с Argon2.PASSWORD_ARGON2ID
Этот алгоритм хеширования также принадлежит к семейству Argon2 и задействует версию Argon2ID, а не I. Чтобы он работал, также необходимо, чтобы РНР был скомпилирован посредством Argon2.
У этой функции также есть необязательный параметр, который состоит из ассоциативного массива, принимающего несколько ключей в соответствии с выбранным алгоритмом.
Если вы предпочитаете использовать Bcrypt, ключ этой последовательности будет значением cost.
Если вы выберете алгоритм, который использует Argon2, ключами для ассоциативного массива будут:
memory_cost
(целое число, указывающее максимальное количество памяти, необходимое для вычисления хэша), time_cost
(целое число, указывающее максимальное время, необходимое для вычисления хэша) и thread
(другое целое число, указывающее количество потоков, используемых при вычислении хэша).Не указывайте параметр
salt
в РНР 7.0, иначе получите предупреждение об устаревшем подходе.Теперь мы знаем, какие элементы необходимы для использования функции
password_hash()
. Давайте посмотрим, как ее прописывать.echo password_hash("MySuperPass", PASSWORD_DEFAULT);
$2y$10$TLayAY8ZaAZ9FE50EylGYO9oEgrb7gsw1yzJemHdBu1gOQfyWrEUm
$options = ['cost' => 12,];
echo password_hash("MySuperPass", PASSWORD_BCRYPT, $options);
$2y$12$jhmTbxAuZXVtX2y.Jc8iy.dW/NENqVCeq2vuoFI9/oa4./YlzhpYO
echo password_hash('rasmuslerdorf', PASSWORD_ARGON2I);
$argon2i$v=19$m=1024,t=2,p=2$YzJBSzV4TUhkMzc3d3laeg$zqU/1IN0/AogfP4cmSJI1vc8lpXRW9/S0sYY2i2jHT0
Сначала рекомендуется протестировать эту функцию на ваших серверах и настроить параметр cost таким образом, чтобы исполнение функции отнимало менее 100 миллисекунд в интерактивных системах.
Скрипт в вышеприведенном примере поможет вам задать оптимальное значение cost для вашего аппаратного оборудования.
Верификация пароля пользователя
Вы дали пользователям возможность зарегистрироваться в вашем новом приложении, они могут ввести туда свой пароль, и вы прекрасно знаете, как с этим паролем обращаться.
Хешируя данные в соответствии с последними трендами безопасности, вы ничего не храните в зашифрованном виде, а ваш сервер спрятан в подвале 10-метровой глубины.
Что теперь?
Теперь вы должны позволить пользователям залогиниться в приложении. Для этого в РНР предусмотрена встроенная функция, которая проверяет соответствие пароля хешированной последовательности. Эта функция носит название
password_verify()
. Работает она примерно вот так:$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';
if (password_verify('rasmuslerdorf', $hash)) {
echo 'Пароль верен!';
} else {
echo 'Пароль не правильный!';
}
У нее есть два параметра, и оба должны иметь формат последовательности. Первый параметр — это пароль, который пользователь ввел в форму входа в аккаунт. Второй параметр — это непосредственно хешированные данные, с которыми мы будем сверяться.
В результате мы получим логическое значение, готовое к использованию в условных операциях. Таким образом, мы можем или пустить пользователя в приложение или сообщить ему, что что-то пошло не так.
Эта функция работает благодаря тому, что на предыдущем шаге (когда мы хешировали пароль), значение, вернувшееся от
password_hash
, включало в себя используемый нами алгоритм, cost
и salt
.Таким образом, нам доступна вся информация, необходимая для
password_verify()
.Алгоритм работы системы регистрации пользователей на РНР
Надеюсь, теперь вы понимаете, какие меры безопасности принимают РНР-разработчики при обращении с паролями.
Сначала необходимо проверить наличие post-запроса, а затем отобрать и подсчитать количество пользователей, чьи данные совпадают с введенными.
Если все прошло успешно, мы верифицируем пароль и отправляем пользователя на стартовую страницу. В противном случае, например, на Javascript выводим окно предупреждения с оповещением об ошибке.
Заключение
Теперь вы знаете, как обеспечить безопасность своему приложению и как правильно обращаться с паролями. Следование полезным рекомендациям — это не просто стандарт, который вы должны соблюдать, а путь развития, следовать которому должно быть приятно.
Осваивайте новые техники по аналогии с тем, как вы только что ознакомились. Добавляйте дополнительный функционал и экспериментируйте с кодом до тех пор, пока не получите отличные скиллы по веб-разработке — будь то РНР или любой другой язык, открывающий перед вами не менее широкие возможности!
Читать первую часть
Комментарии (17)
php7
14.11.2019 21:53-1Напишите, пожалуйста, о том, как работать с авторизационной кукой, что туда писать.
dady_KK
15.11.2019 00:15+1Не ради холивара, а что именно Вы хотите туда писать, например: есть сессионная кука с идентификатором сессии, по которому сервер сверяется с самими сессиями и уже на основании того, что есть в сессии делает выводы. Зачем ещё авторизационная кука, чтобы использовать вместо сессий? Так и сессионная кука насколько я понимаю для этого подойдёт, единственный выигрыш — устанавливать различное время жизни для разных печенек, или есть какие-то ещё плюшки?
VolCh
15.11.2019 06:54Отказаться от сессий в принципе можно, следуя REST архитектуре. Особенно полезно в случае если несколько реплик запущено и лоад балансер перед ними.
php7
15.11.2019 10:06Допустим есть галочка при авторизации «Запомнить меня».
Это что, сессии должны жить год?Mylistryx
15.11.2019 10:15А что сложного? пишем токен (рандомную строку) и храним её в БД на сервере, если пользователь не авторизован, то ищем эту строку в БД и авторизуем пользователя по токену.
Или вы просите автора топика скопипастить вам еще одну статью из интернета?php7
15.11.2019 10:41А чтобы не хранить на сервере?
Mylistryx
15.11.2019 11:20С трудом себе это представляю. Пользователь может менять на своей стороне COOKIE как угодно, значит хранить данные там не безопасно. Т.е. записать в куку ID пользователя или что-то подобное — так себе идея. При очень большом желании можно зашифровать всё это ключем хранимым на сервере и расшифровывать им же, но мне кажется проще вариант с token.
Есть еще вариант с Fingerprint, но я никогда его не использовал, только читал про это, помоему даже здесь, на хабре.
BoShurik
15.11.2019 11:32Например, в Symfony данные в куке подписываются с помощью секретного ключа, чтоб их нельзя было подделать
iig
15.11.2019 11:38Можно в cookie сохранить сертификат SSL со сроком действия в 1 год, и проверять его валидность. А что не так с хранением токена на сервере? + 2 поля (токен и срок действия) в таблице, которая и так есть.
Mylistryx
15.11.2019 11:43В Yii2 даже проще, $token = TOKEN_STRING_TIME
Проверяется через explode('_', $token), сначала TIME, что не протух, потом уже сам токен.
Но я обычно переделываю и храню токены в отдельной таблице с внешним ключем и указанием типа токена. Дает возможность хранить все связанные токены в одной таблице, подчищать протухшие, ограничивать кол-во в целом и по времени.
Но что то мы из комментариев тостер тут устраиваем.
dso
15.11.2019 17:11Тогда токен jwt, который подписывается своим приватным ключом. Токен в бд можно не хранить.
Mylistryx
15.11.2019 23:07Смысл от этого действия? Просто пустить в систему? Если мы не храним этот токен в БД, то к какому пользователю он относится? Выше ссылка на шикарный пример из Symfony!
dso
16.11.2019 00:02Информация о пользователе находится в payload части токена. Гарантией, что токен не подделан, служит сигнатура, которая подписана секретным ключом. Злоумышленник не сможет написать в payload части токена свой email, потому что у него нет секретного ключа, чтобы сгенерировать верную сигнатуру.
Mylistryx
Какой то кусок текста из официальной документации, если честно.
Как насчет password_needs_rehash?
В первый раз слышу про настройку cost. Судя по статье — поставлю я cost=1, менее 100мс? Менее! Зачем её вообще трогать то?
IMHO, статья ради статьи.