Хеширование паролей
Хеширование — это шифрование короткой строки. В результате получается другая строка определённой длины, например 32 символа. Обычно делают хэширование паролей. Способов расшифровки хешированного пароля нет. Возможен только подбор. Поэтому хранение паролей в таком виде безопаснее, чем в обычном.
В PHP есть несколько функций для хэширования строки. Раньше самой распространённой была функция md5() . Но она работает слишком быстро и позволяет достаточно быстро подобрать пароль. Поэтому использовать её нежелательно. Наиболее удобной функцией является password_hash() с алгоритмом хеширования PASSWORD_BCRYPT . Она возвращает строку в зашифрованном виде.
password_hash (пароль, алгоритм, настройки)
пароль — пароль, который хешируется
алгоритм — алгоритм хеширования. Принимает значения: PASSWORD_DEFAULT и PASSWORD_BCRYPT.
настройки — настройки функции. Их менять не рекомендуется
$pas_reg = 'Ivan11'; //пароль, указанный при регистрации $hash = password_hash($pas_reg, PASSWORD_BCRYPT); echo $hash;
Запустите скрипт и посмотрите, как выглядит хешированная строка.
Когда пользователь авторизуется на сайте и вводит пароль, его нужно сравнить с хэшированной строкой. Для этого есть функция password_verify() . Она возвращает true , если строка совпадает с паролем, зашифрованным функцией password_hash() , и false , если строка не соответствует паролю.
password_verify (пароль, хешированная строка)
пароль — пароль, введённый пользователем
хешированная строка — строка, с которой сравнивается пароль
Добавим в скрипт проверку введённого пароля:
$pas_login = 'Ivan11'; //пароль, введённый при авторизации $pr = password_verify($pas_login, $hash); if ($pr) echo '
совпадает'; else echo '
не совпадает';
При хешировании применяется соль. Эта информация не имеет практического значения, потому что функция password_hash() добавляет соль автоматически. Но Вы можете встретить этот термин, и нужно знать, что он означает. Соль — это набор символов, который добавляется к паролю перед хешированием. Она используется потому, что есть возможность расшифровать строку. Это делают с помощью баз данных, которые содержат огромное количество паролей и соответствующих им хешированных строк. Использование соли делает расшифровку сложнее. Расшифрованная строка не совпадает с паролем, потому что она имеет лишние символы. Авторизоваться с помощью расшифрованной строки не получится, она не соответствует паролю.
Хеширование обычно применяется в функциях регистрации и авторизации пользователей.
Всегда нужно использовать хеширование паролей. Хранение паролей в незашифрованном виде делает сайт небезопасным и позволяет злоумышленникам получить доступ к данным пользователей и совершать нежелательные действия с их аккаунтов. Это может испортить репутацию Вашему сайту.
Коприрование материалов сайта возможно только с согласия администрации
2017 — 2024 © basecourse.ru Все права защищены
Безопасное хеширование паролей
В этом разделе разъясняются причины, стоящие за хешированием паролей в целях безопасности, а также эффективные методы хеширования.
- Почему я должен хешировать пароли пользователей в моем приложении?
- Почему популярные хеширующие функции, такие как md5 и sha1 не подходят для паролей?
- Если популярные хеширующие функции не подходят, как же я тогда должен хешировать свои пароли?
- Что такое соль?
- Как я должен хранить свою соль?
Почему я должен хешировать пароли пользователей в моем приложении?
Хеширование паролей является одним из самых основных соображений безопасности, которые необходимо сделать, при разработке приложения, принимающего пароли от пользователей. Без хеширования, пароли, хранящиеся в базе вашего приложения, могут быть украдены, например, если ваша база данных была скомпрометирована, а затем немедленно могут быть применены для компрометации не только вашего приложения, но и аккаунтов ваших пользователей на других сервисах, если они не используют уникальных паролей.
Применяя хеширующий алгоритм к пользовательским паролям перед сохранением их в своей базе данных, вы делаете невозможным разгадывание оригинального пароля для атакующего вашу базу данных, в то же время сохраняя возможность сравнения полученного хеша с оригинальным паролем.
Важно заметить, однако, что хеширование паролей защищает их только от компрометирования в вашем хранилище, но не обязательно от вмешательства вредоносного кода в вашем приложении.
Почему популярные хеширующие функции, такие как md5() и sha1() не подходят для паролей?
Такие хеширующие алгоритмы как MD5, SHA1 и SHA256 были спроектированы очень быстрыми и эффективными. При наличии современных технологий и оборудования, стало довольно просто выяснить результат этих алгоритмов методом «грубой силы» для определения оригинальных вводимых данных.
Из-за той скорости, с которой современные компьютеры могут «обратить» эти хеширующие алгоритмы, многие профессионалы компьютерной безопасности строго не рекомендуют использовать их для хеширования паролей.
Если популярные хеширующие функции не подходят, как же я тогда должен хешировать свои пароли?
При хешировании паролей существует два важных соображения: это стоимость вычисления и соль. Чем выше стоимость вычисления хеширующего алгоритма, тем больше времени требуется для взлома его вывода методом «грубой силы».
Другой возможностью является функция crypt() , которая поддерживает несколько алгоритмов хеширования. При использовании этой функции вы можете быть уверенным, что выбранный вами алгоритм доступен, так как PHP содержит собственную реализацию каждого поддерживаемого алгоритма, даже в случае, если какие-то из них не поддерживаются вашей системой.
При хешировании паролей рекомендуется применять алгоритм Blowfish, который также используется по умолчанию в API хеширования паролей, так как он значительно большей вычислительной сложности, чем MD5 или SHA1, при этом по-прежнему гибок.
Учтите, что, если вы используете функцию crypt() для проверки пароля, то вам нужно предостеречь себя от атак по времени, применяя сравнение строк, которое занимает постоянное время. Ни операторы PHP == и ===, ни функция strcmp() не являются таковыми. Функция же password_verify() как раз делает то, что нужно. Настоятельно рекомендуется использовать встроенное API хеширования паролей, если есть такая возможность.
Что такое соль?
Криптографическая соль представляет собой данные, которые применяются в процессе хеширования для предотвращения возможности разгадать оригинальный ввод с помощью поиска результата хеширования в списке заранее вычисленных пар ввод-хеш, известном также как «радужная» таблица.
Более простыми словами, соль — это кусочек дополнительных данных, которые делают ваши хеши намного более устойчивыми к взлому. Существует много онлайн-сервисов, предоставляющих обширные списки заранее вычисленных хешей вместе с их оригинальным вводом. Использование соли делает поиск результирующего хеша в таком списке маловероятным или даже невозможным.
password_hash() создаёт случайную соль в случае, если она не была передана, и чаще всего это наилучший и безопасный выбор.
Как я должен хранить свою соль?
При использовании функции password_hash() или crypt() , возвращаемое значение уже содержит соль как часть созданного хеша. Это значение нужно хранить как есть в вашей базе данных, так как оно содержит также информацию о хеширующей функции, которая использовалась, и может быть непосредственно передано в функции password_verify() или crypt() при проверке пароля.
Следующая диаграмма показывает формат возвращаемого значения функциями crypt() или password_hash() . Как можно видеть, они содержат полную информацию об алгоритме и соли, требуемых для будущей проверки пароля.
Хеширование пароля на PHP
Хранить пароль в открытом виде — неправильно. Хакер-злоумышленник может получить доступ к вашей базе данных и украсть пароли.
Поэтому обычно логин хранится в открытом виде, а пароль хешируется специальной функцией md5 , которая параметром принимает пароль, а возвращает его , по которому нельзя восстановить этот самый пароль.
Давайте, например, найдем хеш какой-нибудь строки:
Сейчас нам необходимо переделать нашу регистрацию и нашу авторизацию. Для начала я бы советовал очистить таблицу с юзерами, так как там сейчас хранятся пароли в открытом виде, а должны хранится их хеши. Затем при тестировании регистрации таблица заполнится данными в новом формате.
Давайте теперь поправим нашу регистрацию так, чтобы при сохранении нового пользователя в базу добавлялся не пароль, а его хеш.
Описанная правка будет представлять собой что-то такое:
Внесем аналогичные правки в авторизацию:
Внесите изменения в регистрацию с учетом хеширования, зарегистрируйте пару новых пользователей, убедитесь, что в базу данных они добавились с хешированными паролями.
Внесите изменения в авторизацию с учетом хеширования, попробуйте авторизоваться под зарегистрированными ранее пользователями.
crypt
Эта функция (пока) небезопасна для обработки данных в двоичной форме!
Описание
crypt ( string $string , string $salt ): string
crypt() возвращает хешированную строку, полученную с помощью стандартного алгоритма UNIX, основанного на DES или другого алгоритма. Функция password_verify() совместима с crypt() . Следовательно, хеши паролей, созданные crypt() , могут быть использованы в password_verify() .
До версии PHP 8.0.0 параметр salt был необязательным. Однако функция crypt() создаёт слабый хеш без параметра salt , а без него выдаёт ошибку уровня E_NOTICE . Убедитесь, что используете достаточно сложную соль для лучшей безопасности.
Функция password_hash() использует сложный хеш, генерирует сложную соль и применяет правильно количество раундов хеширования автоматически. password_hash() является простой обёрткой над crypt() и совместима с существующими хешами паролей. Поэтому приветствуется использование password_hash() .
Вид хеширования определяется переданным аргументом salt (соль). Если соль не указана, будет автоматически сгенерирована стандартная случайная двухсимвольная (DES) или двенадцатисимвольная (MD5) соль, в зависимости от доступности алгоритма MD5 в crypt(). Предопределённая константа CRYPT_SALT_LENGTH позволяет определить максимально доступную длину соли в соответствии с используемыми алгоритмами.
Стандартная функция crypt() на основе DES возвращает соль в качестве первых двух символов возвращаемой строки. Кроме того, она использует только первые восемь символов строки string , поэтому более длинные строки, начинающиеся с тех же восьми символов, сгенерируют один и тот же результат (при использовании одинаковой соли).
Поддерживаются следующие типы хешей:
- CRYPT_STD_DES — Стандартное DES-шифрование с двухсимвольной солью из алфавита «./0-9A-Za-z»»./0-9A-Za-z». Использование других символов в соли повлечёт за собой отказ работы crypt().
- CRYPT_EXT_DES — Расширенное DES-шифрование. «Соль» является 9-символьной строкой, состоящая из символа подчёркивания, за которым следуют 4 символа счётчика итерации и 4 символа соли. Каждая из этих 4-символьных строк кодирует 24 бита, наименьший символ первым. Значения от 0 до 63 кодируются как ./0-9A-Za-z . Использование недопустимых символов в соли приведёт к ошибке crypt().
- CRYPT_MD5 — MD5-шифрование с 12-символьной солью, начинающейся с $1$
- CRYPT_BLOWFISH — Blowfish-шифрование со следующей солью: «$2a$», «$2x$» или «$2y$», весовой параметр из двух цифр, «$» и 22 цифры из алфавита «./0-9A-Za-z». Использование других символов в соли повлечёт за собой возврат пустой строки. Весовой параметр из двух цифр является двоичным логарифмом счётчика итераций нижележащего хеширующего алгоритма, основанного на Blowfish, и должен быть в диапазоне 04-31, значения вне данного диапазона вызовут отказ crypt(). Хеши «$2x$» потенциально слабые; Хэши «$2a$» совместимы и смягчают эту слабость. Для новых хэшей следует использовать «$2y$».
- CRYPT_SHA256 — хеш SHA-256 с шестнадцатисимвольной солью, начинающейся с $5$. Если строка с солью начинается с ’rounds=$’, число N будет использовано для обозначения количества раундов хеширования, по аналогии с весовым параметром в Blowfish. По умолчанию используемое количество раундов равно 5000, минимально доступно 1000 и максимально 999,999,999. Любое значение вне этого диапазона будет усечено до ближайшего лимита.
- CRYPT_SHA512 — хеш SHA-512 с шестнадцатисимвольной солью, начинающейся с $6$. Если строка с солью начинается с ’rounds=$’, число N будет использовано для обозначения количества раундов хеширования, по аналогии с весовым параметром в Blowfish. По умолчанию используемое количество раундов равно 5000, минимально доступно 1000 и максимально 999,999,999. Любое значение вне этого диапазона будет усечено до ближайшего лимита.
Список параметров
Предостережение
При использовании алгоритма CRYPT_BLOWFISH , параметр string обрезается до 72 байт.
Параметр с солью, на которой будет основано хеширование. Если не указан, поведение определяется по наличию реализованных алгоритмов в системе и может привести к неожиданным результатам.
Возвращаемые значения
Возвращает хешированную строку или строку короче 13 символов, гарантированно отличающуюся от соли в случае возникновения ошибки.
Внимание
При валидации паролей должны использоваться функции сравнения строк, устойчивые к атаке по времени, для сравнения вывода функции crypt() с известным хешом. В PHP для этих целей есть функция hash_equals() .
Список изменений
| Версия | Описание |
|---|---|
| 8.0.0 | salt больше не является необязательным. |
Примеры
Пример #1 Пример использования crypt()
// соль будет сгенерирована автоматически; не рекомендуется
$user_input = ‘rasmuslerdorf’ ;
$hashed_password = ‘$6$rounds=1000000$NJy4rIPjpOaU$0ACEYGg/aKCY3v8O8AfyiO7CTfZQ8/W231Qfh2tRLmfdvFD6XfHk12u6hMr9cYIA4hnpjLNSTRtUwYr9km9Ij/’ ;
?php
// Проверка существующего хеша crypt() способом, совместимым с программами, не использующими PHP.
if ( hash_equals ( $hashed_password , crypt ( $user_input , $hashed_password ))) echo «Пароль верен!» ;
>
?>
Пример #2 Использование crypt() с различными видами хешей
/* Приведённая соль является только примером. Не используйте эту же соль в вашем коде.
Вы должны сгенерировать уникальную и правильную соль для каждого пароля.
*/
echo ‘Стандартный DES: ‘ ,
crypt ( ‘rasmuslerdorf’ , ‘rl’ ),
«\n» ;
echo ‘Расширенный DES: ‘ ,
crypt ( ‘rasmuslerdorf’ , ‘_J9..rasm’ ),
«\n» ;
echo ‘MD5: ‘ ,
crypt ( ‘rasmuslerdorf’ , ‘$1$rasmusle$’ ),
«\n» ;
echo ‘Blowfish: ‘ ,
crypt ( ‘rasmuslerdorf’ , ‘$2a$07$usesomesillystringforsalt$’ ),
«\n» ;
echo ‘SHA-256: ‘ ,
crypt ( ‘rasmuslerdorf’ , ‘$5$rounds=5000$usesomesillystringforsalt$’ ),
«\n» ;
echo ‘SHA-512: ‘ ,
crypt ( ‘rasmuslerdorf’ , ‘$6$rounds=5000$usesomesillystringforsalt$’ ),
«\n» ;
?>?php
Вывод приведённого примера будет похож на:
Стандартный DES: rl.3StKT.4T8M Расширенный DES: _J9..rasmBYk8r9AiWNc MD5: $1$rasmusle$rISCgZzpwk3UhDidwXvin0 Blowfish: $2y$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi SHA-256: $5$rounds=5000$usesomesillystri$KqJWpanXZHKq2BOB43TSaYhEWsQ1Lr5QNyPCDH/Tp.6 SHA-512: $6$rounds=5000$usesomesillystri$D4IrlXatmP7rx3P3InaxBeoomnAihCKRVQP22JZ6EY47Wc6BkroIuUUBOov1i.S5KPgErtP/EN5mcO.ChWQW21
Примечания
Замечание: Функция расшифровки отсутствует, так как crypt() использует необратимый алгоритм хеширования.
Смотрите также
- hash_equals() — Сравнивает строки без риска атаки по времени
- password_hash() — Создаёт хеш пароля
- Страница руководства Unix по вашей функции crypt
User Contributed Notes 8 notes
8 years ago
The #2 comment on this comments page (as of Feb 2015) is 9 years old and recommends phpass. I have independently security audited this product and, while it continues to be recommended for password security, it is actually insecure and should NOT be used. It hasn’t seen any updates in years (still at v0.3) and there are more recent alternatives such as using the newer built-in PHP password_hash() function that are much better. Everyone, please take a few moments to confirm what I’m saying is accurate (i.e. review the phpass code for yourself) and then click the down arrow to sink the phpass comment to the bottom. You’ll be increasing security across the Internet by doing so.
For those who want details: md5() with microtime() are a fallback position within the source code of phpass. Instead of terminating, it continues to execute code. The author’s intentions of trying to work everywhere are admirable but, when it comes to application security, that stance actually backfires. The only correct answer in a security context is to terminate the application rather than fallback to a weak position that can potentially be exploited (usually by forcing that weaker position to happen).
10 years ago
As I understand it, blowfish is generally seen a secure hashing algorithm, even for enterprise use (correct me if I’m wrong). Because of this, I created functions to create and check secure password hashes using this algorithm, and using the (also deemed cryptographically secure) openssl_random_pseudo_bytes function to generate the salt.
/*
* Generate a secure hash for a given password. The cost is passed
* to the blowfish algorithm. Check the PHP manual page for crypt to
* find more information about this setting.
*/
function generate_hash ( $password , $cost = 11 ) /* To generate the salt, first generate enough random bytes. Because
* base64 returns one character for each 6 bits, the we should generate
* at least 22*6/8=16.5 bytes, so we generate 17. Then we get the first
* 22 base64 characters
*/
$salt = substr ( base64_encode ( openssl_random_pseudo_bytes ( 17 )), 0 , 22 );
/* As blowfish takes a salt with the alphabet ./A-Za-z0-9 we have to
* replace any ‘+’ in the base64 string with ‘.’. We don’t have to do
* anything about the ‘=’, as this only occurs when the b64 string is
* padded, which is always after the first 22 characters.
*/
$salt = str_replace ( «+» , «.» , $salt );
/* Next, create a string that will be passed to crypt, containing all
* of the settings, separated by dollar signs
*/
$param = ‘$’ . implode ( ‘$’ ,array(
«2y» , //select the most secure version of blowfish (>=PHP 5.3.7)
str_pad ( $cost , 2 , «0» , STR_PAD_LEFT ), //add the cost in two digits
$salt //add the salt
));
//now do the actual hashing
return crypt ( $password , $param );
>
/*
* Check the password against a hash generated by the generate_hash
* function.
*/
function validate_pw ( $password , $hash ) /* Regenerating the with an available hash as the options parameter should
* produce the same hash if the same password is passed.
*/
return crypt ( $password , $hash )== $hash ;
>
?>
11 years ago
To generate salt use mcrypt_create_iv() not mt_rand() because no matter how many times you call mt_rand() it will only have at most 32 bits of entropy. Which you will start seeing salt collisions after about 2^16 users. mt_rand() is seeded poorly so it should happen sooner.
For bcrypt this will actually generate a 128 bit salt:
*** Bike shed ***
The last character in the 22 character salt is 2 bits.
base64_encode() will have these four character «AQgw»
bcrypt will have these four character «.Oeu»
You don’t need to do a full translate because they «round» to different characters:
echo crypt(», ‘$2y$05$. A’) . «\n»;
echo crypt(», ‘$2y$05$. Q’) . «\n»;
echo crypt(», ‘$2y$05$. g’) . «\n»;
echo crypt(», ‘$2y$05$. w’) . «\n»;
$2y$05$. J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq
$2y$05$. O/jw2XygQa2.LrIT7CFCBQowLowDP6Y.
$2y$05$. eDOx4wMcy7WU.kE21W6nJfdMimsBE3V6
$2y$05$. uMMcgjnOELIa6oydRivPkiMrBG8.aFp.
12 years ago
Here is an expression to generate pseudorandom salt for the CRYPT_BLOWFISH hash type:
The salt created will be 128 bits in length, padded to 132 bits and then expressed in 22 base64 characters. (CRYPT_BLOWFISH only uses 128 bits for the salt, even though there are 132 bits in 22 base64 characters. If you examine the CRYPT_BLOWFISH input and output, you can see that it ignores the last four bits on input, and sets them to zero on output.)
Note that the high-order bits of the four 32-bit dwords returned by mt_rand() will always be zero (since mt_getrandmax == 2^31), so only 124 of the 128 bits will be pseudorandom. I found that acceptable for my application.
10 years ago
The crypt() function cant handle plus signs correctly. So if for example you are using crypt in a login function, use urlencode on the password first to make sure that the login procedure can handle any character:
$user_input = ’12+#æ345′ ;
$pass = urlencode ( $user_input ));
$pass_crypt = crypt ( $pass );
if ( $pass_crypt == crypt ( $pass , $pass_crypt )) echo «Success! Valid password» ;
> else echo «Invalid password» ;
>
?>
6 years ago
While the documentation says that crypt will fail for DES if the salt is invalid, this turns out to not be the case.
The crypt function will accept any string of two characters or more for DES as long as it doesn’t match the pattern for any other hashing schema. The remaining characters will be ignored.
6 years ago
steve at tobtu dot com was right 4 years ago, but now mcrypt_create_iv() (and bcrypt in general) is deprecated!
Use random_bytes() instead:
$salt = base64_encode ( random_bytes ( 16 ));
9 years ago
If you’re stuck with CRYPT_EXT_DES, then you’ll want to pick a number of iterations: the 2nd-5th characters of the «salt».
My experimentation suggests that the 5th character is the most significant. A ‘.’ is a zero and ‘Z’ is the highest value. Using all dots will create an error: all passwords will be encrypted to the same value.
Here are some encryption timings (in seconds) that I obtained, with five different iteration counts over the same salt, and the same password, on a quad core 2.66GHz Intel Xeon machine.
_1111 time: 0.15666794776917
_J9.Z time: 1.8860530853271
_J9.. time: 0.00015401840209961
_. Z time: 1.9095730781555
_ZZZZ time: 1.9124970436096
_. A time: 0.61211705207825
I think a half a second is reasonable for an application, but for the back end authentication? I’m not so sure: there’s a significant risk of overloading the back end if we’re getting lots of authentication requests.
- Copyright © 2001-2024 The PHP Group
- My PHP.net
- Contact
- Other PHP.net sites
- Privacy policy