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

этот вопрос связан с реализацией PHP crypt(). Для этого вопроса первые 7 символов соли не учитываются, поэтому соль'a$a ' можно сказать, имеет длину 1, так как это только 1 символ соли и семь символов метаданных.

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

Пример В Коде:

$foo = 'bar';
$salt_xx = 'a$';
$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) "a$b1b2ee48991281a439d$$.dEUdhUoQXVqUieLTCp0cFVolhFcbuNi"
string(60) "a$b1b2ee48991281a439da$.UxGYN739wLkV5PGoR1XA4EvNVPjwylG"
string(60) "a$b1b2ee48991281a439da2.UxGYN739wLkV5PGoR1XA4EvNVPjwylG"
string(60) "a$b1b2ee48991281a439da2O4AH0.y/AsOuzMpI.f4sBs8E2hQjPUQq"

почему это?

EDIT:

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

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

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

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

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

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

надеюсь, что это помогает.

4 ответов


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

./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$

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

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

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

таким образом, для любых двух соли А и В, где

   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-эпоха время.

$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,'a$' . substr($salt, 0, $i)),
        $i%4 == 0 || $i % 4 == 1 ? ' <-' : '',
        0x0A
    );
}

и это производит примерно следующее

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

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


отличный ответ и четкое разъяснение. Но мне кажется, что либо ошибка в реализации, либо требуется какое-то дополнительное объяснение намерения {комментарии к сообщению объясняют, почему нет ошибки}. The текущая документация 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), и вызывающий объект уже знает полное значение salt, потому что он передал эту строку в качестве аргумента. Но очень трудно понять, почему возвращаемое значение спроектировано так, чтобы оно могло только дайте вам 126 бит 128-битного значения соли. На самом деле, трудно понять, почему он вообще включает входную соль; но опустить 2 бита этого действительно непостижимо.

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

$alphabet = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$lim = strlen($alphabet);
$saltprefix = 'a3456789012345678901'; // 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 ='a3456789012345678901.'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901/'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901A'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901B'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901C'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901D'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901E'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901F'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901G'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901H'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901I'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901J'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901K'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901L'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901M'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'
salt ='a3456789012345678901N'
crypt='a3456789012345678901.YpaB4l25IJ3b3F3H8trjHXj5SC1UbUW'

salt ='a3456789012345678901O'
crypt='a3456789012345678901Ots44xXtSV0f6zMrHerQ2IANdsJ.2ioG'
salty='a3456789012345678901P'
crypt='a3456789012345678901Ots44xXtSV0f6zMrHerQ2IANdsJ.2ioG'
salty='a3456789012345678901Q'
crypt='a3456789012345678901Ots44xXtSV0f6zMrHerQ2IANdsJ.2ioG'
  ... 13 more pairs of output lines with same hash

salt ='a3456789012345678901e'
crypt='a3456789012345678901e.1cixwQ2qnBqwFeEcMfNfXApRK0ktqm'
  ... 15 more pairs of output lines with same hash

salt ='a3456789012345678901u'
crypt='a3456789012345678901u5yLyHIE2JetWU67zG7qvtusQ2KIZhAa'
  ... 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-значная соль ограничивает первую цифру хэша только одним из четырех значений.


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


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

$pass = 'foobarbazqux';
$salt = 'cmfh./TCmc3m0X.MnmHGO';
$cost = 8;
$crypt_salt = sprintf('a$%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) "a$cmfh./TCmc3m0X.MnmHGO."
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGO/"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGO0"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGO1"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGO2"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGO3"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGO4"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGO5"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGO6"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGO7"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGO8"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGO9"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGOA"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOB"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOC"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOD"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOE"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOF"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOG"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOH"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOI"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOJ"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOK"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOL"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOM"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGON"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(60) "a$cmfh./TCmc3m0X.MnmHGO.t0NzWGmKpRimP4RhjFMg3F020kVKG9S"
string(29) "a$cmfh./TCmc3m0X.MnmHGOO"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOP"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOQ"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOR"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOS"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOT"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOU"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOV"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOW"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOX"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOY"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOZ"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOa"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOb"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOc"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOd"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOOSYI2wLIE3NElcU7itPPQnj8iW922mwy"
string(29) "a$cmfh./TCmc3m0X.MnmHGOe"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOf"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOg"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOh"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOi"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOj"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOk"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOl"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOm"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOn"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOo"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOp"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOq"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOr"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOs"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOt"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(60) "a$cmfh./TCmc3m0X.MnmHGOeLcyQf2JnDryc7eA43zx3qi1uJKZUtPK"
string(29) "a$cmfh./TCmc3m0X.MnmHGOu"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGOv"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGOw"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGOx"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGOy"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(29) "a$cmfh./TCmc3m0X.MnmHGOz"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"
string(60) "a$cmfh./TCmc3m0X.MnmHGOutgqolF/BikhkbIM1yMA7HQpkbDxULoG"

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

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

и

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

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