Хеширование пароля на PHP
Хранить пароль в открытом виде — неправильно. Хакер-злоумышленник может получить доступ к вашей базе данных и украсть пароли.
Поэтому обычно логин хранится в открытом виде, а пароль хешируется специальной функцией md5 , которая параметром принимает пароль, а возвращает его , по которому нельзя восстановить этот самый пароль.
Давайте, например, найдем хеш какой-нибудь строки:
Сейчас нам необходимо переделать нашу регистрацию и нашу авторизацию. Для начала я бы советовал очистить таблицу с юзерами, так как там сейчас хранятся пароли в открытом виде, а должны хранится их хеши. Затем при тестировании регистрации таблица заполнится данными в новом формате.
Давайте теперь поправим нашу регистрацию так, чтобы при сохранении нового пользователя в базу добавлялся не пароль, а его хеш.
Описанная правка будет представлять собой что-то такое:
Внесем аналогичные правки в авторизацию:
Внесите изменения в регистрацию с учетом хеширования, зарегистрируйте пару новых пользователей, убедитесь, что в базу данных они добавились с хешированными паролями.
Внесите изменения в авторизацию с учетом хеширования, попробуйте авторизоваться под зарегистрированными ранее пользователями.
Хэширование паролей в PHP 5.5 с использованием нового API
Использование BCrypt является общепринятым и лучшим способом для хэширования паролей, но большое количество разработчиков по-прежнему используют старые и слабые алгоритмы, вроде MD5 и SHA1. Некоторые разработчики даже не используют соль для хэширования. Новый API хэширования в PHP 5.5 ставит своей целью привлечь внимание к BCrypt, упрощая работу с ним. В этой статье я расскажу об основах использования нового API для хеширования в PHP.
- password_hash() — используется для хэширования пароля.
- password_verify() — используется для проверки пароля на соответствие хэшу.
- password_needs_rehash() — используется для проверки необходимости создать новый хэш.
- password_get_info() — возвращает имя алгоритма хеширования и различные параметры, используемые при хэшировании.
password_hash()
Хотя функция crypt() довольно безопасна, она, по мнению многих, слишком сложная. Некоторые разработчики, чтобы не возиться с ней, используют слабую соль и слабый алгоритм для генерирования хэша, например:
Но функция password_hash() позволяет упростить нашу жизнь и обезопасить наш код. Когда вам нужно получить хэш пароля, просто скормите его в эту функцию, и она вернет хэш, который можно хранить в базе данных.
Вот и все! Первым параметром является строка пароля, который необходимо захэшировать, а второй параметр определяет алгоритм, который должен быть использован для генерирования хэша.
Алгоритм по умолчанию, в настоящее время, BCrypt, но более сильный алгоритм может быть установлен по умолчанию, когда-нибудь в будущем, и, возможно, он будет генерировать большие строки. Если вы используете PASSWORD_DEFAULT в ваших проектах, обязательно храните хэш в колонке, размером больше 60 символов. Установка размера колонки до 255 может быть хорошим выбором. Вы также можете использовать PASSWORD_BCRYPT в качестве второго параметра. В этом случае результат всегда будет 60 символов.
Главное здесь в том, что вам не нужно заботиться о значении соли и стоимости вычисления хэша. Новый API будет делать это за вас. И соль является частью хэша, так что вам не придется хранить его отдельно. Если вы хотите использовать вашу собственную соль (или стоимость вычисления), вы можете сделать это путем передачи третьего аргумента функции:
custom_function_for_salt(), //write your own code to generate a suitable salt 'cost' => 12 // the default cost is 10 ]; $hash = password_hash($password, PASSWORD_DEFAULT, $options);
Таким образом, Вы будете всегда идти в ногу с актуальными мерами безопасности. Если PHP позже примет решение о применении более мощного алгоритма хеширования, ваш код может воспользоваться им без изменений.
password_verify()
Теперь, когда вы видели, как генерировать хэши с новым API, давайте посмотрим, как проверить пароль. Мы просто берем хэш из базы, и пароль, введенный пользователем и передаем их в эту функцию. password_verify() возвращает true, если хэш соответствует указанному паролю.
else < // Invalid credentials >
Соль является частью хэша и именно поэтому нам не придется возиться с ней отдельно.
password_needs_rehash()
Что делать, если вам нужно изменить соль или стоимость вычисления для сохраненных паролей? Например, вы решили усилить безопасность и увеличить стоимость вычисления или изменить соль. Или PHP изменил алгоритм хэширования, используемый по умолчанию. В этих случаях вы хотели бы изменить существующие хэши паролей. Функция password_needs_rehash() проверяет, использует ли хэш пароля конкретный алгоритм, соль и стоимость вычисления.
12])) < // the password needs to be rehashed as it was not generated with // the current default algorithm or not created with the cost // parameter 12 $hash = password_hash($password, PASSWORD_DEFAULT, ['cost' =>12]); // don't forget to store the new hash! >
Имейте в виду, что вам нужно сделать это, когда пользователь авторизуется на сайте, так как это единственный раз, когда у вас есть доступ к его незашифрованному паролю.
password_get_info()
- algo — константа, которая идентифицирует конкретный алгоритм
- algoName — название используемого алгоритма
- options — различные опции, используемые при генерации хэша
Заключение
Новый API хэширования паролей, безусловно, удобнее, чем возня с crypt(). Если ваш сайт в настоящее время работает на PHP 5.5, то я настоятельно рекомендуется использовать новое API хэширования. Те, кто используют PHP 5.3.7 (или более новой версии), могут использовать библиотеку под названием password_compat которая эмулирует это API и автоматически отключает себя при использовании PHP версии 5.5+.
- Информационная безопасность
- PHP
password_hash
password_hash() создаёт хеш пароля используя сильный, необратимый алгоритм хеширования.
В данный момент поддерживаются следующие алгоритмы:
- PASSWORD_DEFAULT — используется алгоритм bcrypt (по умолчанию с PHP 5.5.0). Обратите внимание, что используемый алгоритм может со временем меняться на более сильный, когда таковой добавляется в PHP. Соответственно и длина результата может со временем меняться. В связи с этим рекомендуется выбирать длину поля для хранения в базе данных более 60 символов (255 символов могло быть хорошим вариантом).
- PASSWORD_BCRYPT — использует алгоритм CRYPT_BLOWFISH . Генерирует стандартный хеш, совместимый с генерированным функцией crypt() с использованием идентификатора «$2y$». В результате будет сгенерирована строка длиной 60 символов или false в случае возникновения ошибки.
- PASSWORD_ARGON2I — Использовать алгоритм хеширования Argon2i. Этот алгоритм доступен только если PHP собран с поддержкой Argon2.
- PASSWORD_ARGON2ID — Использовать алгоритм хеширования Argon2id. Этот алгоритм доступен только если PHP собран с поддержкой Argon2.
Поддерживаемые опции для PASSWORD_BCRYPT :
-
salt ( string ) — для самостоятельного задания соли для хеширования. Обратите внимание, что это приведёт к переопределению и предотвращению автоматического создания соли. Если не задано, то password_hash() будет генерировать случайную соль для каждого хешируемого пароля. Это предпочтительный режим работы.
Внимание Эта опция объявлена устаревшей. Рекомендуется использовать автоматически генерируемую соль. Начиная с PHP 8.0.0 явно заданная соль игнорируется.
Поддерживаемые опции для PASSWORD_ARGON2I и PASSWORD_ARGON2ID :
- memory_cost ( int ) — Максимальный размер памяти (в килобайтах), которую можно использовать для вычисления хеша Argon2. По умолчанию PASSWORD_ARGON2_DEFAULT_MEMORY_COST .
- time_cost ( int ) — Максимально возможное время, которое можно потратить для вычисления хеша Argon2. По умолчанию PASSWORD_ARGON2_DEFAULT_TIME_COST .
- threads ( int ) — Количество потоков, которые можно использовать для вычисления хеша Argon2. По умолчанию PASSWORD_ARGON2_DEFAULT_THREADS .
Внимание Доступно только тогда, когда PHP использует libargon2, но не при реализации libsodium.
Список параметров
Предостережение
Использование алгоритма PASSWORD_BCRYPT приведёт к обрезанию поля password до максимальной длины 72 байта.
Константа, обозначающая используемый алгоритм хеширования пароля.
Ассоциативный массив с опциями. За документацией по поддерживаемым опциям для каждого алгоритма обратитесь к разделу Константы алгоритмов хеширования паролей.
Если не задано, то будет использована стандартная стоимость и соль будет генерироваться автоматически.
Возвращаемые значения
Возвращает хешированный пароль.
Использованный алгоритм, стоимость и соль будут возвращены как часть хеша. Таким образом, информация, необходимая для проверки хеша будет в него включена. Это позволит функции password_verify() проверять хеш без необходимости отдельного хранения информации о соли и алгоритме.
Список изменений
| Версия | Описание |
|---|---|
| 8.0.0 | password_hash() больше не возвращает значение false в случае возникновения ошибки, вместо этого будет выброшено исключение ValueError , если алгоритм хеширования пароля недействителен или Error , если хеширование пароля не удалось из-за неизвестной ошибки. |
| 8.0.0 | Параметр algo теперь допускает значение null . |
| 7.4.0 | Параметр algo сейчас ожидает строку ( string ), но всё ещё принимает число ( int ) для обратной совместимости. |
| 7.4.0 | Модуль sodium обеспечивает альтернативную реализацию паролей Argon2. |
| 7.3.0 | Добавлена поддержка алгоритма хеширования паролей Argon2id с помощью PASSWORD_ARGON2ID . |
| 7.2.0 | Добавлена поддержка хеширующего алгоритма Argon2i с помощью PASSWORD_ARGON2I . |
Примеры
Пример #1 Пример использования password_hash()
/**
* Мы просто хотим захешировать свой пароль используя настройки по умолчанию.
* Значит будет использован BCRYPT и результат будет 60 символов длиной.
*
* Помните, что алгоритм по умолчанию может измениться в будущем, так что
* имеет смысл заранее позаботиться о том, чтобы система хранения хешей
* смогла хранить более 60 символов (255 в самый раз)
*/
echo password_hash ( «rasmuslerdorf» , PASSWORD_DEFAULT );
?>?php
Результатом выполнения данного примера будет что-то подобное:
$2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a
Пример #2 Пример использования password_hash() с ручным заданием стоимости
/**
* Тут мы увеличиваем алгоритмическую стоимость BCRYPT до 12.
* Но это никак не скажется на длине полученного результата, она останется 60 символов
*/
$options = [
‘cost’ => 12 ,
];
echo password_hash ( «rasmuslerdorf» , PASSWORD_BCRYPT , $options );
?>?php
Результатом выполнения данного примера будет что-то подобное:
$2y$12$QjSH496pcT5CEbzjD/vtVeH03tfHKFy36d4J0Ltp3lRtee9HDxY3K
Пример #3 Пример поиска хорошего значения стоимости для password_hash()
/**
* Данный код замерит скорость выполнения операции хеширования для вашего сервера
* с разными значениями алгоритмической сложности для определения максимального
* его значения, не приводящего к деградации производительности. Хорошее базовое
* значение лежит в диапазоне 8-10, но если ваш сервер достаточно мощный, то можно
* задать и больше. Данный скрипт ищет максимальное значение, при котором
* хеширование уложится в 50 миллисекунд.
*/
$timeTarget = 0.05 ; // 50 миллисекунд.
?php
$cost = 8 ;
do $cost ++;
$start = microtime ( true );
password_hash ( «test» , PASSWORD_BCRYPT , [ «cost» => $cost ]);
$end = microtime ( true );
> while (( $end — $start ) < $timeTarget );
echo «Оптимальная стоимость: » . $cost ;
?>
Результатом выполнения данного примера будет что-то подобное:
Оптимальная стоимость: 10
Пример #4 Пример использования password_hash() с Argon2i
echo ‘Хеш Argon2i: ‘ . password_hash ( ‘rasmuslerdorf’ , PASSWORD_ARGON2I );
?>?php
Результатом выполнения данного примера будет что-то подобное:
Хеш Argon2i: $argon2i$v=19$m=1024,t=2,p=2$YzJBSzV4TUhkMzc3d3laeg$zqU/1IN0/AogfP4cmSJI1vc8lpXRW9/S0sYY2i2jHT0
Примечания
Предостережение
Настоятельно рекомендуется использовать автоматическую генерацию соли. Данная функция самостоятельно создаст хорошую соль, если вы не будете ей мешать подсовывая свою.
Как было замечено выше, опция salt была объявлена устаревшей в PHP 7.0 и будет вызывать соответствующее предупреждение. Поддержка ручного задания соли может быть удалена в более новых версиях.
Замечание:
Рекомендуется протестировать данную функцию на вашем железе для определения оптимального значения алгоритмической сложности. Убедитесь, что с выбранной сложностью функция выполняется быстрее 100 миллисекунд для интерактивных систем. Скрипт показанный выше поможет вам выбрать подходящее значение.
- Любой новый алгоритм должен присутствовать в ядре как минимум 1 полный релиз PHP для того, чтобы его можно было установить по умолчанию. Таким образом, если, к примеру, новый алгоритм был добавлен в 7.5.5, то задать по умолчанию его можно будет только в 7.7 (7.6 будет тем самым полным релизом, в течение которого он должен присутствовать, от 7.6.0 до 7.7.0). Но если новый алгоритм добавлен в 7.6.0, то его также можно будет задать по умолчанию в 7.7.0.
- Алгоритм по умолчанию может быть изменён только в полном релизе (7.3.0, 8.0.0, и т.д.), но не в промежуточных. Единственное исключение — это если в текущем алгоритме найдена критическая уязвимость.
Смотрите также
- password_verify() — Проверяет, соответствует ли пароль хешу
- password_needs_rehash() — Проверяет, что указанный хеш соответствует заданным опциям
- crypt() — Необратимое хеширование строки
- sodium_crypto_pwhash_str() — Получить ASCII-кодированный хеш
User Contributed Notes 10 notes
10 years ago
There is a compatibility pack available for PHP versions 5.3.7 and later, so you don’t have to wait on version 5.5 for using this function. It comes in form of a single php file:
https://github.com/ircmaxell/password_compat
4 years ago
Since 2017, NIST recommends using a secret input when hashing memorized secrets such as passwords. By mixing in a secret input (commonly called a «pepper»), one prevents an attacker from brute-forcing the password hashes altogether, even if they have the hash and salt. For example, an SQL injection typically affects only the database, not files on disk, so a pepper stored in a config file would still be out of reach for the attacker. A pepper must be randomly generated once and can be the same for all users. Many password leaks could have been made completely useless if site owners had done this.
Since there is no pepper parameter for password_hash (even though Argon2 has a «secret» parameter, PHP does not allow to set it), the correct way to mix in a pepper is to use hash_hmac(). The «add note» rules of php.net say I can’t link external sites, so I can’t back any of this up with a link to NIST, Wikipedia, posts from the security stackexchange site that explain the reasoning, or anything. You’ll have to verify this manually. The code:
// register.php
$pepper = getConfigVariable ( «pepper» );
$pwd = $_POST [ ‘password’ ];
$pwd_peppered = hash_hmac ( «sha256» , $pwd , $pepper );
$pwd_hashed = password_hash ( $pwd_peppered , PASSWORD_ARGON2ID );
add_user_to_database ( $username , $pwd_hashed );
?>
// login.php
$pepper = getConfigVariable ( «pepper» );
$pwd = $_POST [ ‘password’ ];
$pwd_peppered = hash_hmac ( «sha256» , $pwd , $pepper );
$pwd_hashed = get_pwd_from_db ( $username );
if ( password_verify ( $pwd_peppered , $pwd_hashed )) echo «Password matches.» ;
>
else echo «Password incorrect.» ;
>
?>
Note that this code contains a timing attack that leaks whether the username exists. But my note was over the length limit so I had to cut this paragraph out.
Also note that the pepper is useless if leaked or if it can be cracked. Consider how it might be exposed, for example different methods of passing it to a docker container. Against cracking, use a long randomly generated value (like in the example above), and change the pepper when you do a new install with a clean user database. Changing the pepper for an existing database is the same as changing other hashing parameters: you can either wrap the old value in a new one and layer the hashing (more complex), you compute the new password hash whenever someone logs in (leaving old users at risk, so this might be okay depending on what the reason is that you’re upgrading).
Why does this work? Because an attacker does the following after stealing the database:
password_verify(«a», $stolen_hash)
password_verify(«b», $stolen_hash)
.
password_verify(«z», $stolen_hash)
password_verify(«aa», $stolen_hash)
etc.
(More realistically, they use a cracking dictionary, but in principle, the way to crack a password hash is by guessing. That’s why we use special algorithms: they are slower, so each verify() operation will be slower, so they can try much fewer passwords per hour of cracking.)
Now what if you used that pepper? Now they need to do this:
password_verify(hmac_sha256(«a», $secret), $stolen_hash)
Without that $secret (the pepper), they can’t do this computation. They would have to do:
password_verify(hmac_sha256(«a», «a»), $stolen_hash)
password_verify(hmac_sha256(«a», «b»), $stolen_hash)
.
etc., until they found the correct pepper.
If your pepper contains 128 bits of entropy, and so long as hmac-sha256 remains secure (even MD5 is technically secure for use in hmac: only its collision resistance is broken, but of course nobody would use MD5 because more and more flaws are found), this would take more energy than the sun outputs. In other words, it’s currently impossible to crack a pepper that strong, even given a known password and salt.
PHP hash: хэширование паролей в PHP
Хранить пароли пользователей в открытом виде в БД — не самый лучший вариант, особенно когда к этой базе данных могут получить доступ другие. И, правда, при взломе базы пароли пользователей, хранящиеся в открытом виде, будут банально украдены, а это уже чревато серьёзными репутационными и финансовыми потерями, чего не может допустить никакой системный администратор.
В этой статье будет рассмотрен процесс хэширования паролей (hashing) — процесс несложный, но, к сожалению, порой игнорируемый веб-программистами.
Да, хэширование панацеей не является, но всё же при краже данных оно может существенно снизить ущерб. Ведь что собой представляет хэш-функция (hash, хэш, хеш)? Речь идёт о функции, которая обладает бесконечной областью определения, имея при этом конечную область значения. А ещё функции типа hash присуща одна интересная особенность: даже в случае небольшого изменения входного значения у хэш-функции, итоговое значение изменится просто радикально. Если говорить языком криптографию, стоит отметить главное назначение хэш-функций — генерация ключей на основе запоминаемых и коротких паролей (лично вам, легко ли было бы запомнить 16 шестнадцатеричных разрядов?) То-то же.
Подытожив первую часть статьи, скажем, что hash генерирует хэш-код.
Описание hash в PHP
Описать хэш (хеш) в PHP можно следующим образом:
hash ( string $algo , string $data [, bool $raw_output = FALSE ] ) : stringТеперь стоит рассказать про список параметров функций типа hash (хеш, хэш): • algo. Тут прописывается название алгоритма, выбранного для хэширования (тот же md5 или SHA-1). Существует целый список поддерживаемых алгоритмов, найти который не составляет труда; • data. Сообщение для хэширования. Без комментариев; • raw_output. Если этот параметр установлен в режим TRUE, то осуществляется вывод необработанных двоичных данных. Если же выставить его в FALSE, производится вывод данных в шестнадцатеричной кодировке (плюс это осуществляется в нижнем регистре).
Возвращаемые значения
При работе функции hash выполняется возврат строки, содержащей вычисленный код в нижнем регистре и в шестнадцатеричной кодировке. Но если raw_output будет задан как TRUE, произойдёт возврат кода в виде бинарных данных.
PHP хэш: хэширование MD5
В качестве продолжения статьи, стоит отдельно поговорить про хэширование по алгоритму MD5. Это будет неплохой пример, да и тема эта в целом весьма важна, а чтение не займёт у вас много времени. Но для начала скажем пару слов о самом алгоритме. MD5 (Message Digest 5) представляет собой 128-битный алгоритм хеширования, который был разработан Рональдом Ривестом в далёком 1991 г. Описан он в RFC 1321 (RFC, если не знаете, — это «Request for Comments» — техническая спецификация и стандарт, широко применяемый в глобальной сети).
Простейший пример практического применения хэша MD5 — шифрование пользовательских паролей. Как уже было сказано, хранить пароли в БД в открытом виде не просто моветон, а в принципе недопустимо. И вот здесь как раз и может прийти на помощь функция хэширования в PHP и соответствующий алгоритм.
В языке программирования PHP функция хэширования MD5 называется md5() . Она принимает одну строку, которую надо зашифровать. Возвращает же MD5-хэш (hash). На практике это может выглядеть следующим образом:
Если вы запустите этот скрипт, увидите MD5-хэш, который будет соответствовать строке "Password".
Теперь простейший скрипт для проверки пароля и логина:
Если вы теперь перейдёте по ссылке "http://путь_к_скрипту.php?login=Admin&password=Password", вы увидите: "Добро пожаловать!".
Свойства MD5
У вышерассмотренного алгоритма есть следующие свойства: • hash содержит 32 символа; • hash уникален для каждой строки; • сам процесс MD5-хэширования является необратимым; • этот процесс отличается достаточной медлительностью.
Что касается третьего пункта, то он весьма важен, ведь какой был бы смысл в хэшировании, если бы оно было обратимым? И, действительно, не глупо ли шифровать пасворды, которые потом легко и просто можно расшифровать? Вопрос риторический.
Но если мы пойдём дальше, то увидим, что пункт номер 4 в нашем списке — это тоже плюс, как бы это не звучало парадоксально. Дело в том, что если человек, набирающий пароль, вынужден подождать 0,001 секунды (именно столько и длится процесс), то для него это не проблема — он от этого не пострадает и даже не заметит этой самой медлительности. Если же мы говорим про хакера, которому надо перебрать множество вариантов, то медлительность алгоритма придётся ему очень не кстати, ведь алгоритм задаёт предел — 1000 паролей в секунду. Да, многое зависит и от быстродействия сервера, но несмотря на это, низкая скорость при получении хеша MD5 — это всё-таки проблема для злоумышленника.
На этом закончим и в очередной раз напомним: никогда не храните ваши пароли и пароли ваших пользователей в открытом виде — просто забудьте, что так можно делать, даже (и тем более) если вы обычный веб-мастер. Всегда отправляйте ваши пароли в БД в виде hash (алгоритм выбирайте на своё усмотрение) и сравнивайте не сами пасворды, а их хэши.
Интересует PHP? Добро пожаловать на курс:
