Php как сделать короткий хэш

Обновлено: 07.07.2024

Эта функция (пока) небезопасна для обработки данных в двоичной форме!

Описание

crypt ( string $string , string $salt ): string

crypt() возвращает хешированную строку, полученную с помощью стандартного алгоритма UNIX, основанного на DES или другого алгоритма.

Параметр salt является необязательным. Однако без salt функция crypt() создаёт слабый хеш. Если не использовать salt , выдаётся ошибка E_NOTICE . Убедитесь, что используете достаточно сложную соль для лучшей безопасности.

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

Вид хеширования определяется переданным аргументом salt (соль). Если соль не указана, будет автоматически сгенерирована стандартная случайная двухсимвольная (DES) или двенадцатисимвольная (MD5) соль, в зависимости от доступности алгоритма MD5 в crypt(). Предопределённая константа CRYPT_SALT_LENGTH позволяет определить максимально доступную длину соли в соответствии с используемыми алгоритмами.

Стандартная функция crypt() на основе DES возвращает соль в качестве первых двух символов возвращаемой строки. Кроме того, она использует только первые восемь символов строки string , поэтому более длинные строки, начинающиеся с тех же восьми символов, сгенерируют один и тот же результат (при использовании одинаковой соли).

Поддерживаются следующие типы хешей:

Список параметров

При использовании алгоритма CRYPT_BLOWFISH , параметр string обрезается до 72 символов.

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

Возвращаемые значения

Возвращает хешированную строку или строку короче 13 символов, гарантированно отличающуюся от соли в случае возникновения ошибки.

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

Список изменений

Версия Описание
8.0.0 salt больше не является необязательным.

Примеры

<?php
// соль будет сгенерирована автоматически; не рекомендуется
$hashed_password = crypt ( 'mypassword' );

/* Для проверки пароля в качестве параметра salt следует передавать результат работы
crypt() целиком во избежание проблем при использовании различных
алгоритмов (как уже было отмечено выше, стандартный DES-алгоритм
использует 2-символьную соль, а MD5 - 12-символьную. */
if ( hash_equals ( $hashed_password , crypt ( $user_input , $hashed_password ))) echo "Пароль верен!" ;
>
?>

<?php
// пароль
$password = 'mypassword' ;

// получение хеша, соль генерируется автоматически; не рекомендуется
$hash = crypt ( $password );
?>

<?php
/* Приведённая соль является только примером. Не используйте эту же соль в вашем коде.
Вы должны сгенерировать уникальную и правильную соль для каждого пароля.
*/
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" ;
?>

Результатом выполнения данного примера будет что-то подобное:

Примечания

Замечание: Функция расшифровки отсутствует, так как crypt() использует необратимый алгоритм хеширования.

Смотрите также

User Contributed Notes 8 notes

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).

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.

<?php
/*
* 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 ;
>
?>

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:
<?php $salt = strtr ( base64_encode ( mcrypt_create_iv ( 16 , MCRYPT_DEV_URANDOM )), '+' , '.' ); ?>

*** 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.

Here is an expression to generate pseudorandom salt for the CRYPT_BLOWFISH hash type:

<?php $salt = substr ( str_replace ( '+' , '.' , base64_encode ( pack ( 'N4' , mt_rand (), mt_rand (), mt_rand (), mt_rand ()))), 0 , 22 ); ?>

It is intended for use on systems where mt_getrandmax() == 2147483647.

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.

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:

if ( $pass_crypt == crypt ( $pass , $pass_crypt )) echo "Success! Valid password" ;
> else echo "Invalid password" ;
>
?>

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:

<?php
$salt = base64_encode ( random_bytes ( 16 ));

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

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.

Главная причина - минимизация ущерба в случае утечки базы данных.

Если злоумышленник получит только логины и email-адреса пользователей - это плохо, но не критично.

В тот же личный кабинет Пятёрочки, чтобы перевыпустить карту и потратить чужие бонусы.

Мем с бабкой - Где мои баллы?!

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

Некоторые разработчики считают, что их приложение надёжно защищено и никаких утечек быть не может. Есть несколько причин, почему это мнение ошибочно:

  • Разработчик - не робот, он не может не совершать ошибок.
  • Взлом может произойти со стороны хостинг-провайдера, работу которого не всегда возможно контролировать.
  • Некорректная настройка сервера может привести к возможному доступу других пользователей хостинга к вашему сайту (актуально для виртуальных хостингов).
  • Бывший коллега по работе может слить базу данных конкурентам. Может в качестве мести, а может просто ради денег.

Короче, пароли в открытом виде хранить нельзя.

Шифрование и хеширование

Шифрование - это обратимое преобразование текста в случайный набор символов.

Хеширование - это необратимое преобразование текста в случайный набор символов.

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

Зашифровали. Теперь для расшифровки нужно выполнить обратную операцию, сдвинуть все буквы на 1 символ назад. К слову, этот алгоритм шифрования называется шифр Цезаря (Википедия).

В отличие от шифрования, хеширование не имеет (вернее, не должно иметь) способа "расхешировать" строку обратно:

Шифрование паролей

Не надо шифровать пароли.

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

Хеширование паролей и авторизация

Для хеширования паролей в PHP существует функция password_hash() :

Вторым параметром передаётся алгоритм хеширования. По-умолчанию это указанный нами bcrypt, но я рекомендую указывать его вручную, поскольку базовый алгоритм в будущем может поменяться. Будет грустно, если при очередном обновлении версии PHP на сайте отвалится авторизация.

Для проверки корректности введённого пользователем пароля используется функция password_verify() :

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

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

Таким образом, хранить исходный пароль больше нет смысла.

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

Алгоритмы MD5 и SHA1

В интернете ещё встречаются статьи, где рекомендуется хешировать пароли функциями md5() и sha1() .

Для хеширования паролей их использовать нельзя!

Эти алгоритмы давно устарели и не являются безопасными (Документация). Вместо этого используйте функцию password_hash() , которую мы разобрали выше.

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

например, в Python:

Если вам не нужен алгоритм, который силен против преднамеренной модификации, я нашел алгоритм под названием adler32 это дает довольно короткие (

8 символов) результаты. Выберите его из выпадающего списка здесь, чтобы попробовать его:

Итак, используя что-то вроде CRC-64 (и base-64'ing результате) следует получить вас в районе, который вы ищете.

вы можете использовать существующий алгоритм хэша, который производит что-то короткое, например MD5 (128 бит) или SHA1 (160). Затем вы можете сократить это еще больше, XORing разделы дайджеста с другими разделами. Это увеличит вероятность столкновений, но не так плохо, как просто усечение дайджеста.

кроме того, можно добавить длину исходных данных как часть результата, чтобы сделать его более уникальным. Например, XORing первой половины дайджеста MD5 со второй половиной будет результат в 64 бит. Добавьте 32 бита для длины данных (или ниже, если вы знаете, что длина всегда будет вписываться в меньшее количество битов). Это приведет к 96-битному (12-байтовому) результату, который вы можете превратить в 24-символьную шестнадцатеричную строку. В качестве альтернативы вы можете использовать кодировку base 64, чтобы сделать ее еще короче.

просто суммируя ответ, который был мне полезен (отмечая комментарий @erasmospunk об использовании кодировки base-64). Моя цель состояла в том, чтобы иметь короткую строку, которая была в основном уникальный.

Я не эксперт, поэтому, пожалуйста, исправьте это, если у него есть какие-либо вопиющие ошибки (в Python снова, как принятый ответ):

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

вы можете использовать библиотеку hashids, которая имеет реализации для PHP, Javascript, Python и т. д. Для получения более подробной информации проверьте этой ссылке

Если вам нужно "sub-10-character hash" вы могли бы использовать Флетчер-32 алгоритм, который производит 8 символов хэша (32 бита),CRC-32 или Адлер-32.

CRC-32 медленнее, чем Adler32 в 20% - 100%.

Флетчер-32 несколько надежнее, чем Адлер-32. Он имеет более низкую вычислительную стоимость, чем контрольная сумма Adler:сравнение Флетчер и Адлер.

пример программы с несколькими Fletcher реализации приведены ниже:

недавно мне нужно было что-то вроде простой функции сокращения строки. В принципе, код выглядел примерно так (код C / C++ впереди):

он, вероятно, имеет больше коллизий, чем можно было бы пожелать, но он не предназначен для использования в качестве криптографической хэш-функции. Вы можете попробовать различные множители (т. е. изменить 37 на другое простое число), если у вас слишком много столкновений. Одна из интересных особенностей этого фрагмента заключается в том, что когда Src короче чем Dest, Dest заканчивается входной строкой as-is (0 * 37 + value = value). Если вы хотите что-то" читаемое " в конце процесса, Normalize отрегулирует преобразованные байты за счет увеличения коллизий.

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

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

Давайте, например, найдем хеш какой-нибудь строки:

<?php echo md5('12345'); //выведет '827ccb0eea8a706c4c34a16891f84e7b' ?>

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

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

Описанная правка будет представлять собой что-то такое:

<?php $login = $_POST['login']; $password = md5($_POST['password']); // преобразуем пароль в его хеш $query = "INSERT INTO users SET login='$login', password='$password'"; ?>

Внесем аналогичные правки в авторизацию:

<?php $login = $_POST['login']; $password = md5($_POST['password']); // преобразуем пароль в его хеш $query = "SELECT * FROM users WHERE login='$login' AND password='$password'"; ?>

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

Добавляем соль в регистрацию

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

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

Речь идет о достаточно простых, популярных паролях.

Загуглите, например, хеш 827ccb0eea8a706c4c34a16891f84e7b и сразу в поиске гугла вы увидите, что это пароль '12345' .

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

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

Мы можем при регистрации заставлять придумывать более длинные пароли, ограничивая, к примеру, минимальное количество символов 6 -ю или 8 -ю, однако, все равно будут появляться пароли вида '123456' или '12345678' .

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

Суть этого решения такая: пароли надо . Соль - это специальная случайная строка, которая будет добавляться к паролю при регистрации и хеш уже будет вычисляться не от простого пароля типа, а от строки соль+пароль, то есть от соленого пароля.

То есть при регистрации вы будете делать что-то типа такого:

<?php $salt = '1sJg3hfdf'; // соль - сложная случайная строка $password = md5($salt . $_POST['password']); // преобразуем пароль в соленый хеш ?>

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

Вот готовая функция, которая сделает это:

С помощью этой функции можно переписать наш код вот так:

<?php $salt = generateSalt(); // соль $password = md5($salt . $_POST['password']); // соленый пароль ?>

Еще раз повторю, что это были изменения при регистрации - в БД сохраняем не просто хеш пароля, а хеш соленого пароля.

Это еще не все: в таблице с юзерами кроме поля login и password нужно сделать еще и поле salt , в котором мы будем хранить соль каждого пользователя.

Реализуйте описанную выше регистрацию с соленым паролем.

Добавляем соль в авторизацию

Теперь нам необходимо поменять авторизацию. Здесь уже изменения будут более существенными.

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

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

Учтите, что может такое быть, что логин вбит неправильно, в этом случае проверку пароля можно не осуществлять, а сразу вывести, что авторизация не возможна - данные не верны:

Давайте добавим проверку пароля:

Реализуйте описанную выше авторизацию с соленым паролем. Попробуйте зарегистрируйтесь, авторизуйтесь, убедитесь, что все работает.

Используем функцию password_hash

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

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

Попробуйте несколько раз запустите этот код:

Вы каждый раз будете получать разный результат и в этом результате первая часть строки будет являться солью, а вторая часть - соленым паролем.

Пусть у нас есть хеш, полученный из функции password_hash и какой-то пароль. Чтобы проверить, это хеш этого пароля или нет, следует использовать функцию password_verify - первым параметром она принимает пароль, а вторым - хеш, и возвращает true или false .

Давайте посмотрим на примере:

<?php $password = '12345'; // пароль $hash = '$2y$10$xoYFX1mFPxBSyxaRe3iIRutxkIWhxGShzEhjYUVd3qpCUKfJE1k7a'; // хеш if (password_verify($password, $hash)) < // хеш от этого пароля >else < // хеш не от этого пароля >?>

Что это дает нам на практике: мы можем не создавать в базе данных отдельное поле для хранения соли, не заморачиваться с генерированием этой соли - PHP все сделает за нас!

Теперь давайте поправим код регистрации. Вот то, что есть сейчас:

<?php function generateSalt() < $salt = ''; $saltLength = 8; // длина соли for($i = 0; $i < $saltLength; $i++) < $salt .= chr(mt_rand(33, 126)); // символ из ASCII-table >return $salt; > $salt = generateSalt(); // соль $password = md5($salt . $_POST['password']); // преобразуем пароль в соленый хеш ?>

С помощью password_hash мы сократим это до:

<?php $password = password_hash($_POST['password'], PASSWORD_DEFAULT); ?>

Аналогичным образом подправится код авторизации:

Переделайте вашу авторизацию и регистрацию на новые изученные функции.

password_hash (при первом чтении можно пропустить). В нем мы указываем алгоритм шифрования. На самом деле, этот параметр зарезервирован создателями PHP на будущее.

В настоящее время алгоритмом по умолчанию будет алгоритм BCrypt (то есть аналог md5, но мощнее), который своим результатом возвращает строку соль+хеш размером 60 символов.

Это значит, что поле password в базе данных следует установить такого размера - 60 символов, иначе ничего не будет работать верно.

Алгоритм BCrypt можно указать явно во втором параметре функции password_hash , написав там PASSWORD_BCRYPT .

Если же оставить там PASSWORD_DEFAULT - то PHP возьмет алгоритм по умолчанию, то есть тот же BCrypt - но это в настоящее время. В будущем возможно BCrypt устареет также, как и md5 и будет заменен более мощным алгоритмом.

В таком случае изменения в вашем коде произойдут автоматически при переходе на новую версию PHP.

Читайте также: