Подтвердить что ты не робот

Почему crypt/blowfish генерируют один и тот же хэш с двумя разными солями?

Этот вопрос связан с реализацией PHP crypt(). По этому вопросу первые 7 символов соли не учитываются, поэтому соль "$2a$07$a" будет иметь длину 1, так как это всего лишь 1 символ соли и семь символов метаданных.

При использовании сольных строк длиной более 22 символов не изменяется изменение хэша (т.е. усечение), и при использовании строк длиной менее 21 символа соль будет автоматически дополняться (с символами < <22 > ); это довольно просто. Однако, если задана соль 20 символов и соль 21 символа, где эти два идентичны, за исключением окончательного символа соли длиной 21 длины, обе хешированные строки будут одинаковыми. Соль длиной 22 символа, которая идентична соли длины 21, за исключением конечного символа, хэш снова будет другим.

Пример в коде:

$foo = 'bar';
$salt_xx = '$2a$07$';
$salt_19 = $salt_xx . 'b1b2ee48991281a439d';
$salt_20 = $salt_19 . 'a';
$salt_21 = $salt_20 . '2';
$salt_22 = $salt_21 . 'b';

var_dump(
    crypt($foo, $salt_19), 
    crypt($foo, $salt_20), 
    crypt($foo, $salt_21), 
    crypt($foo, $salt_22)
);

Будет производить:

string(60) "$2a$07$b1b2ee48991281a439d$$.dEUdhUoQXVqUieLTCp0cFVolhFcbuNi"
string(60) "$2a$07$b1b2ee48991281a439da$.UxGYN739wLkV5PGoR1XA4EvNVPjwylG"
string(60) "$2a$07$b1b2ee48991281a439da2.UxGYN739wLkV5PGoR1XA4EvNVPjwylG"
string(60) "$2a$07$b1b2ee48991281a439da2O4AH0.y/AsOuzMpI.f4sBs8E2hQjPUQq"

Почему это?

EDIT:

Некоторые пользователи отмечают, что существует разница в общей строке, что верно. В salt_20 смещение (28, 4) составляет da$., а в salt_21 смещение (28, 4) равно da2.; однако важно отметить, что генерируемая строка включает в себя хеш, соль, а также инструкции для получения соли (т.е. $2a$07$); часть, в которой происходит различие, по-прежнему остается солью. Фактический хэш не изменяется как UxGYN739wLkV5PGoR1XA4EvNVPjwylG.

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

Rembmer: выход будет в следующем формате:

"$2a$##$saltsaltsaltsaltsaltsaHASHhashHASHhashHASHhashHASHhash"
//                            ^ Hash Starts Here, offset 28,32

где ## - это логарифмическая база-2, определяющая количество итераций, выполняемых алгоритмом для

Изменить 2:

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

var_dump(
    PHP_VERSION, 
    PHP_OS, 
    CRYPT_SALT_LENGTH, 
    CRYPT_STD_DES, 
    CRYPT_EXT_DES, 
    CRYPT_MD5, 
    CRYPT_BLOWFISH
);

Производит следующий вывод:

string(5) "5.3.0"
string(5) "WINNT"
int(60)
int(1)
int(1)
int(1)
int(1)

Надеюсь, что это поможет.

4b9b3361

Ответ 1

После некоторых экспериментов я пришел к выводу, что это связано с тем, как обрабатывается соль. Соль не считается литеральным текстом, а скорее представляет собой кодированную в base64 строку, так что 22 байта данных соли фактически представляют собой 16-байтовую строку (floor(22 * 24 / 32) == 16) соли. "Готча!" с этой реализацией, однако, заключается в том, что, подобно склепу Unix, он использует "нестандартный" алфавит base64. Если быть точным, он использует этот алфавит:

./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$

65-й символ, '$', является символом заполнения.

Теперь функция crypt(), по-видимому, способна принимать соль любой длины, меньшей или равной ее максимальному, и молча обрабатывать любые несоответствия в базе64 путем отбрасывания любых данных, которые не составляют другого полного байта, Функция склепа полностью завершится, если вы передадите ей символы в соли, которые не являются частью его алфавита base64, что просто подтверждает эту теорию его работы.

Возьмите воображаемую соль '1234'. Это идеально соответствует base64, поскольку он представляет 24 бита данных, поэтому 3 байта, и не несет никаких данных, которые необходимо отбросить. Это соль, которая Len Mod 4 равна нулю. Добавьте любой символ к этой соли, и он станет солью из 5 символов, а Len Mod 4 теперь равен 1. Однако этот дополнительный символ представляет только шесть бит данных и поэтому не может быть преобразован в другой полный байт, поэтому он отбрасывается.

Таким образом, для любых двух солей A и B, где

   Len A Mod 4 == 0 
&& Len B Mod 4 == 1  // these two lines mean the same thing
&& Len B = Len A + 1 // but are semantically important separately
&& A == substr B, 0, Len A

Фактическая соль, используемая crypt() для вычисления хэша, фактически будет идентичной. В качестве доказательства я включаю пример кода PHP, который можно использовать для показа этого. Соль постоянно вращается неспецифическим способом semi (на основе рандомизированного сегмента хэша вихревого потока текущего времени до микросекунды) и данных, которые должны быть хэшированы (здесь и называется $seed), это просто текущее время Unix-Epoch.

$salt = substr(hash('whirlpool',microtime()),rand(0,105),22);
$seed = time();
for ($i = 0, $j = strlen($salt); $i <= $j; ++$i) {
    printf('%02d = %s%s%c',
        $i,
        crypt($seed,'$2a$07$' . substr($salt, 0, $i)),
        $i%4 == 0 || $i % 4 == 1 ? ' <-' : '',
        0x0A
    );
}

И это дает результат, похожий на следующий

00 = $2a$07$$$$$$$$$$$$$$$$$$$$$$.rBxL4x0LvuUp8rhGfnEKSOevBKB5V2. <-
01 = $2a$07$e$$$$$$$$$$$$$$$$$$$$.rBxL4x0LvuUp8rhGfnEKSOevBKB5V2. <-
02 = $2a$07$e8$$$$$$$$$$$$$$$$$$$.WEimjvvOvQ.lGh/V6HFkts7Rq5rpXZG
03 = $2a$07$e89$$$$$$$$$$$$$$$$$$.Ww5p352lsfQCWarRIWWGGbKa074K4/.
04 = $2a$07$e895$$$$$$$$$$$$$$$$$.ZGSPawtL.pOeNI74nhhnHowYrJBrLuW <-
05 = $2a$07$e8955$$$$$$$$$$$$$$$$.ZGSPawtL.pOeNI74nhhnHowYrJBrLuW <-
06 = $2a$07$e8955b$$$$$$$$$$$$$$$.2UumGVfyc4SgAZBs5P6IKlUYma7sxqa
07 = $2a$07$e8955be$$$$$$$$$$$$$$.gb6deOAckxHP/WIZOGPZ6/P3oUSQkPm
08 = $2a$07$e8955be6$$$$$$$$$$$$$.5gox0YOqQMfF6FBU9weAz5RmcIKZoki <-
09 = $2a$07$e8955be61$$$$$$$$$$$$.5gox0YOqQMfF6FBU9weAz5RmcIKZoki <-
10 = $2a$07$e8955be616$$$$$$$$$$$.hWHhdkS9Z3m7/PMKn1Ko7Qf2S7H4ttK
11 = $2a$07$e8955be6162$$$$$$$$$$.meHPOa25CYG2G8JrbC8dPQuWf9yw0Iy
12 = $2a$07$e8955be61624$$$$$$$$$.vcp/UGtAwLJWvtKTndM7w1/30NuYdYa <-
13 = $2a$07$e8955be616246$$$$$$$$.vcp/UGtAwLJWvtKTndM7w1/30NuYdYa <-
14 = $2a$07$e8955be6162468$$$$$$$.OTzcPMwrtXxx6YHKtaX0mypWvqJK5Ye
15 = $2a$07$e8955be6162468d$$$$$$.pDcOFp68WnHqU8tZJxuf2V0nqUqwc0W
16 = $2a$07$e8955be6162468de$$$$$.YDv5tkOeXkOECJmjl1R8zXVRMlU0rJi <-
17 = $2a$07$e8955be6162468deb$$$$.YDv5tkOeXkOECJmjl1R8zXVRMlU0rJi <-
18 = $2a$07$e8955be6162468deb0$$$.aNZIHogUlCn8H7W3naR50pzEsQgnakq
19 = $2a$07$e8955be6162468deb0d$$.ytfAwRL.czZr/K3hGPmbgJlheoZUyL2
20 = $2a$07$e8955be6162468deb0da$.0xhS8VgxJOn4skeI02VNI6jI6324EPe <-
21 = $2a$07$e8955be6162468deb0da3.0xhS8VgxJOn4skeI02VNI6jI6324EPe <-
22 = $2a$07$e8955be6162468deb0da3ucYVpET7X/5YddEeJxVqqUIxs3COrdym

Вывод? Двукратное. Во-первых, он работает по назначению, а во-вторых, знает свою соль или не откатывает свою соль.

Ответ 2

Отличный ответ и ясное объяснение. Но мне кажется, что есть либо ошибка в реализации, либо какое-то дополнительное объяснение намерения (комментарии к сообщению объясняют, почему нет ошибки). В текущей документации php говорится:

CRYPT_BLOWFISH - хеширование Blowfish с солью следующим образом: "$ 2a $", двухзначный параметр стоимости, "$" и 22 базовых 64 цифры из алфавита "./0-9A-Za-z". Использование символов вне этого диапазона в соли приведет к тому, что crypt() вернет строку нулевой длины. Параметр стоимости двух цифр является логарифмом базы-2 итерации для базового алгоритма хэширования на основе Blowfish и должен находиться в диапазоне 04-31, значения вне этого диапазона вызовут сбой crypt().

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

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

Но, как показано в ответе Dereleased, если строка входных солей действительна, выход состоит из входной соли, заполненной до фиксированной длины с '$', при этом к нему добавляется 32-символьная вычисленная хэш-величина. К сожалению, соль в результате дополняется только 21 base64 цифрами, а не 22! Это показано последними тремя строками в этом ответе, где мы видим один "$" для 20 цифр, не "$" для 21, а когда в соли 22 знака base64, первый символ хеш-результата заменяет 22-я цифра входной соли. Функция все еще пригодна для использования, поскольку полное значение, которое он вычисляет, доступно вызывающему абоненту как substr(crypt($pw,$salt), 28, 32), и вызывающий абонент уже знает полное значение соли, поскольку он передал эту строку в качестве аргумента. Но очень сложно понять, почему обратное значение сконструировано так, что оно может дать вам только 126 бит 128-битного значения соли. На самом деле, трудно понять, почему он включает входную соль вообще; но пропуская 2 бита, это действительно непостижимо.

Вот небольшой фрагмент, показывающий, что 22-я base64-цифра вносит только два бита в соль, фактически используемую при вычислении (есть только 4 отдельных хеши):

$alphabet = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$lim = strlen($alphabet);
$saltprefix = '$2a$04$123456789012345678901'; // 21 base64 digits


for ($i = 0; $i < $lim; ++$i ) {
  if ($i = 16 || $i == 32 || $i == 48) echo "\n";
  $salt = $saltprefix . substr($alphabet, $i, 1);
  $crypt = crypt($password, $salt);
  echo "salt ='$salt'\ncrypt='$crypt'\n";
}

salt ='$2a$04$123456789012345678901.'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901/'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901A'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901B'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901C'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901D'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901E'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901F'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901G'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901H'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901I'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901J'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901K'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901L'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901M'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='$2a$04$123456789012345678901N'
crypt='$2a$04$123456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'

salt ='$2a$04$123456789012345678901O'
crypt='$2a$04$123456789012345678901Ots44xXtSV0f6zMrHerQ2IANdsJ.2ioG'
salty='$2a$04$123456789012345678901P'
crypt='$2a$04$123456789012345678901Ots44xXtSV0f6zMrHerQ2IANdsJ.2ioG'
salty='$2a$04$123456789012345678901Q'
crypt='$2a$04$123456789012345678901Ots44xXtSV0f6zMrHerQ2IANdsJ.2ioG'
  ... 13 more pairs of output lines with same hash

salt ='$2a$04$123456789012345678901e'
crypt='$2a$04$123456789012345678901e.1cixwQ2qnBqwFeEcMfNfXApRK0ktqm'
  ... 15 more pairs of output lines with same hash

salt ='$2a$04$123456789012345678901u'
crypt='$2a$04$123456789012345678901u5yLyHIE2JetWU67zG7qvtusQ2KIZhAa'
  ... 15 more pairs of output lines with same hash

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

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

substr(crypt($pw,$salt), -32)

В качестве окончательной заметки, в то время как объяснение того, почему хеш-значение повторяется, когда количество символов base64, указанных mod 4 == 1, имеет смысл с точки зрения того, почему код может вести себя таким образом, он не объясняет, почему писать код таким образом была хорошей идеей. Код может и, возможно, должен включать в себя биты из знака base64, который составляет частичный байт при вычислении хеша, вместо того, чтобы просто отбрасывать их. Если бы код был написан таким образом, то, скорее всего, проблема с потерей 22-й цифры соли на выходе тоже не появлялась. {Поскольку комментарии к сообщению объясняют, даже если 22-я цифра перезаписана, цифра хэша, которая ее перезаписывает, будет только одним из четырех возможных значений [. Oeu], и это только значимые значения для 22-й цифры. Если 22-я цифра не является одним из этих четырех значений, она будет заменена на одну из четырех, которая создает тот же хеш.}

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

Ответ 3

Похоже, что выходы на самом деле разные. (da $, vs da2) для результата salt_20 и salt_21.

Ответ 4

Из моего исследования казалось, что соль всегда 22 символа, а смещение хэша - 29, а не 28, что составляет 31 символ в длину, а не 32. Я запустил этот код:

$pass = 'foobarbazqux';
$salt = 'cmfh./TCmc3m0X.MnmHGO';
$cost = 8;
$crypt_salt = sprintf('$2a$%02d$%s', $cost, $salt);
$chars = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
for ($i = 0; $i < strlen($chars); $i++) {
    $hash = crypt($pass, $crypt_salt . $chars[$i]);
    var_dump($crypt_salt . $chars[$i], $hash, crypt($pass, $hash));
}

Результаты:

string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO."
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO/"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO0"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO1"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO2"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO3"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO4"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO5"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO6"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO7"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO8"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGO9"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOA"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOB"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOC"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOD"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOE"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOF"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOH"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOI"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOJ"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOL"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOM"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGON"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOO"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOP"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOQ"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOR"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOS"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOT"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOU"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOV"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOW"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOX"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOY"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOZ"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOa"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOb"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOc"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOd"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOe"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOf"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOg"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOh"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOi"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOj"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOk"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOl"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOm"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOn"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOo"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOp"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOq"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOr"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOs"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOt"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOu"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOv"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOw"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOx"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOy"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "$2a$08$cmfh./TCmc3m0X.MnmHGOz"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "$2a$08$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"

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

$hash_to_store = crypt($new_password, $formatted_salt);

и

$verified = $stored_hash == crypt($entered_password, $stored_hash);

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