Отличный ответ и четкое объяснение. Но мне кажется, что в реализации есть либо ошибка, либо требуется какое-то дальнейшее объяснение намерения {комментарии к посту объясняют, почему ошибки нет}. текущая документация php заявляет:
CRYPT_BLOWFISH - Хеширование Blowfish с солью следующим образом: "$ 2a $", двухзначный параметр стоимости "$" и 22 64 цифры из алфавита "./0-9A-Za-z". Использование символов вне этого диапазона в соли приведет к тому, что crypt () вернет строку нулевой длины. Двузначный параметр стоимости - это логарифм base-2 числа итераций для базового алгоритма хеширования на основе Blowfish, и он должен находиться в диапазоне 04-31, значения вне этого диапазона вызовут ошибку crypt ().
Это соответствует тому, что было сказано и продемонстрировано здесь. К сожалению, документация не описывает возвращаемое значение очень полезно:
Возвращает хешированную строку или строку, которая короче 13 символов и гарантированно будет отличаться от соли при ошибке.
Но, как показано в ответе Прервано , если входная строка соли действительна, вывод состоит из входной соли, дополненной до фиксированной длины с символами '$', с 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-значная соль ограничивает первую цифру хеш-значения только одним из четырех значений.