Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Mhash: полный справочник старого API и перенос на Hash
  2. Старый экспорт перестал работать: сначала разберём, что он вычислял
  3. Где находится Mhash и почему extension_loaded вводит в заблуждение
  4. Как читать E_DEPRECATED в учебных примерах
  5. Все 39 констант: точное соответствие современным именам
  6. Практика переноса: digest, HMAC и формат транспорта
  7. Старый ключ из пароля: что можно заменить сразу, а что требует миграции
  8. Карта функций и порядок изменения старого проекта
  9. Функции и методы этой темы
  10. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 88 · Поддержка старого проекта

Mhash: полный справочник старого API и перенос на Hash

Все пять функций и 39 констант Mhash: диагностика deprecation, бинарный формат, HMAC, Salted S2K и проверяемая миграция.

Старый экспорт перестал работать: сначала разберём, что он вычислял

Наглядная модель

Сохранить старый формат при переходе на Hash

Старая интеграция подписывает одно сообщение Mhash. Для обновления важны те же байты, алгоритм и представление результата.

  1. Сообщение abc

    Фиксируем точные входные байты и отсутствие HMAC-ключа.

  2. MHASH_SHA256

    Старый вызов возвращает 32 raw-байта SHA-256.

  3. hash('sha256', …, true)

    Современный вызов с true тоже возвращает raw-байты.

  4. Сравнение fixture

    Сначала проверяем совпадение результата; смена алгоритма или формата — отдельное изменение протокола.

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

Вы открыли модуль обмена старого магазина и встретили mhash(MHASH_SHA256, $data). Нужно обновить PHP, сохранив чтение старых файлов и подписи партнёра. Первая задача — понять входные байты, алгоритм и формат результата. Переименование функции без этой проверки может сломать обмен даже при правильном новом алгоритме.

Безопасный порядок исследования
  1. Старый пример: зафиксировать сообщение и ожидаемые байты
  2. Узнать digest или HMAC, вариант алгоритма и raw/hex/base64
  3. Выполнить соответствующий Hash-вызов на той же fixture
  4. Совпадение → перенос API; смена алгоритма → отдельная версия протокола

Начнём с современного простого вызова без устаревших конструкций. hash — имя функции. В скобках "sha256" выбирает алгоритм, "abc" передаёт сообщение. Знак = сохраняет результат в переменной. По умолчанию результат hash — печатная строка hex, а echo выводит её.

Получить понятный результат современным HashСамостоятельный пример
<?php
declare(strict_types=1);

$digest = hash('sha256', 'abc');
echo $digest, "\n";
echo strlen($digest), "\n";
Ожидаемый результат
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
64

Вход здесь вымышленный и несекретный. digest помогает сопоставить содержимое, но без доверенного ожидаемого значения сам не подтверждает автора сообщения. Один hex-символ кодирует четыре бита; два — один байт.

Исторический Mhash использует числовые константы алгоритмов и возвращает raw-байты. Все его функции исследуются ниже с измеримым результатом. Если старого формата в проекте нет, используйте полный современный Hash; для аккаунтов — хеширование паролей. Не нужно осваивать legacy API перед написанием первого сайта.

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

Где находится Mhash и почему extension_loaded вводит в заблуждение

Современная PHP предоставляет Mhash как слой совместимости расширения Hash; отдельная внешняя библиотека libmhash для этого слоя не требуется. С PHP 7.0 Mhash полностью интегрирован в Hash и больше не регистрируется самостоятельным расширением. Поэтому extension_loaded("mhash") и поиск имени в get_loaded_extensions не являются проверкой доступности mhash().

Проверка среды старого приложения
  1. Нужный процесс PHP: CLI или веб-SAPI
  2. function_exists("mhash") → есть ли совместимость
  3. Hash доступен, но Mhash мог не включаться при сборке
  4. Планируем перенос на hash_*, а не добавляем случайную DLL
Проверить слой совместимости без устаревшего вызоваСамостоятельный пример
<?php
declare(strict_types=1);

var_dump(extension_loaded('hash'));
var_dump(extension_loaded('mhash'));
var_dump(function_exists('mhash'));
var_dump(defined('MHASH_SHA256'));
Ожидаемый результат
bool(true)
bool(false)
bool(true)
bool(true)

Это фактический вывод проверенной PHP 8.5.11. false для имени расширения не противоречит доступности функции. defined только проверяет существование имени; чтение самой устаревшей константы отдельно создаёт diagnostic.

Официальный узелПравило установки
RequirementsВнешняя библиотека не нужна: функции предоставляет совместимость Hash.
InstallationДля включения совместимости при сборке указан --with-mhash. Этот configure-параметр устарел с PHP 8.1 и предупреждает при сборке.
Hash и готовые дистрибутивыHash всегда доступен в современном PHP, но наличие mhash* проверяется отдельно. У Windows/готового пакета состав определяется сборщиком; строка extension=mhash не является универсальным решением.
Настройка процессаУ семейства нет отдельной страницы runtime configuration или своего набора INI-настроек. CLI и веб-сервер могут запускать разные бинарники и конфигурации.

Не применяйте старые советы «установить php5-mhash» или добавить mhash.so к PHP 8.5 без понимания версии и сборки. Для планового обновления сначала подготовьте замену API, контрольные примеры и проверку сохранённых данных. Закрытая диагностика окружения должна выполняться именно там, где запускается интеграция, а не на другом компьютере.

Как читать E_DEPRECATED в учебных примерах

Устаревший вызов в PHP 8.5 может дать правильный результат и одновременно сообщить о необходимости миграции. Это не один взаимоисключающий исход «сработало или ошибка». В примерах явно учитываются E_DEPRECATED от функций mhash* и констант MHASH_*, а остальные сообщения остаются обычной обработке PHP.

Событие и результат — две отдельные вещи
  1. PHP сообщает об устаревшем символе
  2. Лабораторный обработчик отмечает это сообщение
  3. Функция возвращает результат либо отказывает по своим правилам
  4. finally восстанавливает обработчик, последняя строка сообщает о deprecation

Обвязка использует set_error_handler(function (...) {...}): передаёт PHP функцию, которую следует вызвать при сообщении. use (&$legacyNotice) позволяет ей изменить флаг снаружи. Она возвращает true только для ожидаемого сообщения Mhash, false — передаёт другие сообщения обычному механизму. try/finally обеспечивает восстановление обработчика после опыта. Это уже вспомогательный приём; сам исследуемый вызов находится внутри try.

Устаревший вызов возвращает значение и создаёт сообщениеСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $digest = mhash(MHASH_SHA256, 'abc');
    echo strlen($digest), "\n";
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
32
Устаревший API: да

Последняя строка вывода показывает, что ожидаемое предупреждение было замечено. Счётчик всех предупреждений не нужен: число чтений констант и вызовов зависит от примера. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

В приложении следует сохранять диагностические сообщения в закрытом журнале и устранять зависимость. Не переносите лабораторный перехват в production как «исправление», не выключайте E_ALL и не добавляйте @ перед каждым вызовом. Ключи, пароли и полные HTTP payload не нужны в журнале deprecation. В отличие от современных hash_hmac/password_hash, старые сигнатуры mhash и mhash_keygen_s2k в PHP 8.5 не помечают секретный аргумент SensitiveParameter: не рассчитывайте на автоматическое скрытие в трассировке.

Все 39 констант: точное соответствие современным именам

Константы MHASH_* имеют тип int, доступны только при включённом слое совместимости и все устарели с PHP 8.5. Таблица фиксирует соответствия PHP 8.5.11; она нужна для миграции, а не для выбора алгоритма по порядковому номеру. Числа полезны при чтении старого сохранённого формата, но новый код использует имена Hash.

Константа переводится через явную таблицу
  1. MHASH_HAVAL256 → ID 3
  2. Старая таблица указывает haval256,3
  3. Hash-вызов сохраняет этот вариант
  4. Смена варианта означает другой digest
Старое имя и IDТочное имя HashВерсия / назначение
MHASH_CRC32 · ID 0crc32Исторический API; Некриптографический checksum/хеш
MHASH_MD5 · ID 1md5Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_SHA1 · ID 2sha1Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_HAVAL256 · ID 3haval256,3Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_RIPEMD160 · ID 5ripemd160Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_TIGER · ID 7tiger192,3Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_GOST · ID 8gostИсторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_CRC32B · ID 9crc32bИсторический API; Некриптографический checksum/хеш
MHASH_HAVAL224 · ID 10haval224,3Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_HAVAL192 · ID 11haval192,3Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_HAVAL160 · ID 12haval160,3Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_HAVAL128 · ID 13haval128,3Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_TIGER128 · ID 14tiger128,3Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_TIGER160 · ID 15tiger160,3Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_MD4 · ID 16md4Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_SHA256 · ID 17sha256Исторический API; Криптографический digest; назначение зависит от протокола
MHASH_ADLER32 · ID 18adler32Исторический API; Некриптографический checksum/хеш
MHASH_SHA224 · ID 19sha224Исторический API; Криптографический digest; назначение зависит от протокола
MHASH_SHA512 · ID 20sha512Исторический API; Криптографический digest; назначение зависит от протокола
MHASH_SHA384 · ID 21sha384Исторический API; Криптографический digest; назначение зависит от протокола
MHASH_WHIRLPOOL · ID 22whirlpoolИсторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_RIPEMD128 · ID 23ripemd128Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_RIPEMD256 · ID 24ripemd256Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_RIPEMD320 · ID 25ripemd320Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_SNEFRU256 · ID 27snefru256Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_MD2 · ID 28md2Исторический API; Исторический вариант; не выбор защиты по одному имени
MHASH_FNV132 · ID 29fnv132Исторический API; Некриптографический checksum/хеш
MHASH_FNV1A32 · ID 30fnv1a32Исторический API; Некриптографический checksum/хеш
MHASH_FNV164 · ID 31fnv164Исторический API; Некриптографический checksum/хеш
MHASH_FNV1A64 · ID 32fnv1a64Исторический API; Некриптографический checksum/хеш
MHASH_JOAAT · ID 33joaatИсторический API; Некриптографический checksum/хеш
MHASH_CRC32C · ID 34crc32cPHP 7.4+; Некриптографический checksum/хеш
MHASH_MURMUR3A · ID 35murmur3aPHP 8.1+; Некриптографический checksum/хеш
MHASH_MURMUR3C · ID 36murmur3cPHP 8.1+; Некриптографический checksum/хеш
MHASH_MURMUR3F · ID 37murmur3fPHP 8.1+; Некриптографический checksum/хеш
MHASH_XXH32 · ID 38xxh32PHP 8.1+; Некриптографический checksum/хеш
MHASH_XXH64 · ID 39xxh64PHP 8.1+; Некриптографический checksum/хеш
MHASH_XXH3 · ID 40xxh3PHP 8.1+; Некриптографический checksum/хеш
MHASH_XXH128 · ID 41xxh128PHP 8.1+; Некриптографический checksum/хеш

В диапазоне 0…41 пусты ID 4, 6 и 26. Не создавайте несуществующие MHASH_* по догадке. Старые названия не описывают полный каталог современного Hash: например, одно семейство может иметь дополнительные варианты в hash_algos(). Для HMAC список ещё уже: проверьте hash_hmac_algos.

MD2/MD4/MD5/SHA-1 и другие исторические варианты в этой таблице документируют старый формат; присутствие в библиотеке не означает пригодность для новых задач защиты. CRC, Adler, FNV, JOAAT, Murmur и xxHash не предназначены для противодействия умышленной подмене. Более длинный вывод сам по себе не даёт криптографическую гарантию.

Проверить несколько важных соответствий на одних данныхСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $cases = [[MHASH_SHA256, 'sha256'], [MHASH_TIGER, 'tiger192,3'],
              [MHASH_HAVAL128, 'haval128,3'], [MHASH_XXH128, 'xxh128']];
    foreach ($cases as [$legacyId, $name]) {
        echo $name, ': ', mhash($legacyId, 'catalog fixture') === hash($name, 'catalog fixture', true) ? 'OK' : 'FAIL', "\n";
    }
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
sha256: OK
tiger192,3: OK
haval128,3: OK
xxh128: OK
Устаревший API: да

XXH128 здесь проверяется только как некриптографический формат. Вычисление одинакового результата не является доказательством безопасности алгоритма. Полный каталог и все 39 соответствий дополнительно проверены авторским тестом этой главы. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Практика переноса: digest, HMAC и формат транспорта

Официальный раздел Examples показывает digest и HMAC в hex. Сохраним учебную цель, но используем собственные входные данные и современный SHA-256. Сначала отметьте, что хранится в старой БД/пакете: бинарные байты, hex или base64. Затем сравнивайте одинаковые представления.

Смена API не должна менять транспортный формат
  1. Строка заказа и серверный ключ
  2. HMAC даёт бинарный результат
  3. Протокол требует base64 → кодируем raw-байты
  4. На новой реализации проверяем ту же строку base64
Не перепутать base64 от digest и от hexСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $body = 'order=42&status=paid';
    $key = 'demo-transport-key';
    $oldEncoded = base64_encode(mhash(MHASH_SHA256, $body, $key));
    $newEncoded = base64_encode(hash_hmac('sha256', $body, $key, true));
    $wrongEncoded = base64_encode(hash_hmac('sha256', $body, $key));
    var_dump($oldEncoded === $newEncoded);
    var_dump($oldEncoded === $wrongEncoded);
    echo strlen($oldEncoded), '/', strlen($wrongEncoded), "\n";
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
bool(true)
bool(false)
44/88
Устаревший API: да

Третий вариант кодирует 64 ASCII-символа hex вместо 32 исходных байтов. Base64 — представление, а не шифрование; кодирование не скрывает секрет и не добавляет подписи. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Если ключ хранится в hex, сначала проверьте его формат и декодируйте hex2bin; если хранится обычная строка, не декодируйте её по догадке. Согласуйте кодировку текста, переносы строк, порядок полей, способ сериализации и представление подписи. У паролей и ключей нет автоматического trim: изменение байтов меняет криптографическую операцию.

Для большого файла не загружайте весь файл в строку только ради замены mhash: hash_file или hash_init → hash_update_stream → hash_final поддерживают соответствующие маршруты. Формат и обработку ошибок всё равно надо сохранить; benchmark сравнивает одинаковый объём данных и одинаковую задачу.

Старый ключ из пароля: что можно заменить сразу, а что требует миграции

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

Граница миграции зашифрованного архива
  1. Версия старого контейнера выбирает точный S2K-контракт
  2. Старая соль и пароль → старый ключ → чтение старого формата
  3. После проверки содержимого создаётся новый формат и ключ
  4. Версия нового контейнера сохраняет свои параметры, соль и nonce

Соль хранится рядом с метаданными, потому что без неё восстановить ключ нельзя; соль не является паролем. Сам производный ключ рядом со шифртекстом в открытом виде не хранится. Фраза «добавить соль к ключу» в старой документации не означает, что безопасно раскрыть этот ключ. Одно наличие KDF не добавляет аутентификацию шифртексту.

ПотребностьСледующий инструмент
Проверять пароль аккаунтаpassword_hash / password_verify; это не ключ для шифрования.
Случайный новый секретный ключКриптографический генератор случайных байтов; хранение по отдельной политике секретов.
Ключ из уже сильного секретаhash_hkdf с контекстом назначения; не средство замедления угадывания человеческого пароля.
Ключ из пароля по заданному протоколуhash_pbkdf2 либо согласованная современная password KDF; параметры и ограничения выбираются по протоколу и актуальной политике.
Прочитать существующий Salted S2Kmhash_keygen_s2k в ограниченном миграторе с контрольными векторами; это не новый рекомендуемый формат.

Демонстрации в карточке показывают дополнение/усечение соли, несколько digest-блоков и неэквивалентность PBKDF2. Здесь нет универсального дешифратора: формат архива, режим шифрования, проверка целостности и управление ключами требуют отдельного точного договора. Не пишите «совместимый» новый KDF без векторов реального формата и проверки его владельца.

Карта функций и порядок изменения старого проекта

Что встретилось в кодеРазбор
Вычисление digest или HMACmhash
Перебор IDmhash_count
Чтение длины поляmhash_get_block_size
Название старого вариантаmhash_get_hash_name
Ключ из старого пароля/солиmhash_keygen_s2k
  • Записать тестовые входы и точные ожидаемые байты, включая пустые строки, NUL, переводы строк и Unicode; реальные секреты в fixtures не переносить.
  • Разделить замену API и смену алгоритма/формата. Для hash/hash_hmac явно сохранить binary=true, когда старый вызов возвращал raw.
  • Проверить каждый используемый старый вариант через явную таблицу; не применять strtolower ко всем историческим именам.
  • Обработать false и исключения до bin2hex, strlen и сравнения. В новом Hash неизвестное имя может вызывать ValueError вместо старого false.
  • Не считать отсутствие E_DEPRECATED доказательством безопасности протокола. Удаление предупреждения и обновление криптографической схемы — разные задачи.

Упражнение: старый партнёр ожидает hex-подпись SHA-256. Напишите современный вызов, который сохранит эту строку. Проверка должна сравнить свою fixture, а не отправлять данные партнёру.

Решение: сохранить hex после замены APIСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $message = 'invoice=42';
    $key = 'demo-exercise-key';
    $old = bin2hex(mhash(MHASH_SHA256, $message, $key));
    $replacement = hash_hmac('sha256', $message, $key);
    var_dump(hash_equals($old, $replacement));
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
bool(true)
Устаревший API: да

По умолчанию hash_hmac уже возвращает hex, поэтому дополнительный bin2hex сделал бы двойное кодирование. При сравнении реальной подписи отдельно проверьте её формат и длину, а затем используйте hash_equals. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Дальше: современный Hash, пароли аккаунтов, ядро интеграционного сервиса.

Функции и методы этой темы

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

mhashВоспроизвести бинарный digest или HMAC старого протокола и проверить современную замену.

PHP 4+; устарела с PHP 8.1 · hash

function mhash(int $algo, string $data, ?string $key = null): string|false

Перенести проверку старой выгрузки на Hash. Старый вызов принимает числовую константу и возвращает бинарную строку. Чтобы сравнить с новым Hash без изменения формата, третьим аргументом hash указываем true.

Путь конкретных данных
  1. Задан старый формат SHA256 и учебные байты
  2. Вызов mhash проверяет конкретную часть совместимости
  3. Миграция сохраняет байты либо явно останавливается

Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.

Воспроизвести бинарный digest или HMAC старого протокола и проверить современную замену.

Параметр / результатКонтракт
algoЧисловой ID через MHASH_*; неверный ID или незанятая позиция возвращает false. Не заменяйте ID строкой sha256: это другой интерфейс.
dataВсе байты сообщения, включая NUL, пробелы и переводы строк. mhash получает готовую строку; потоковой обработки у этого вызова нет.
keyПропущен или null — обычный digest. Любая строка, включая пустую, включает HMAC. Явный nullable-контракт с PHP 8.0. Ключ берут из серверной конфигурации, не из недоверенного запроса.
ВозвратБинарная строка digest/HMAC либо false для неизвестного ID. Для печатного hex используется bin2hex(); для base64 кодируются именно исходные байты результата.
Ошибки и версииВ PHP 8.5 не подходящий для HMAC алгоритм, например CRC32, вызывает ValueError; неверный тип при strict_types — TypeError. Поэтому string|false не обещает отсутствие исключений. Вызов даёт E_DEPRECATED с 8.1; константа — с 8.5.

Сначала прочтите вызов: первый аргумент выбирает алгоритм, второй передаёт сообщение. $raw = ... сохраняет результат. Слово raw здесь означает «исходные байты», а не текст из нулей и единиц. Указание true третьим аргументом современного hash сохраняет этот формат.

Перенести проверку старой выгрузки на HashСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $data = file_get_contents(__DIR__ . '/invoice.txt');
    if ($data === false) { throw new RuntimeException('Выгрузка недоступна'); }
    $old = mhash(MHASH_SHA256, $data);
    $new = hash('sha256', $data, true);
    echo 'invoice.txt: ', bin2hex($new), "\n";
    echo hash_equals($old, $new) ? "Старый формат подтверждён; получатель примет тот же digest.\n" : "Миграция меняет байты.\n";
} finally { restore_error_handler(); }
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
invoice.txt: 36c43bb2eda40f4ea02f6ef2b7a4d0dbf7754df0c0f2c88fb1b120d6d2b1bdb2
Старый формат подтверждён; получатель примет тот же digest.
Устаревший API: да

Обработчик предупреждений временно собирает только ожидаемые deprecation Mhash; finally восстанавливает обычную обработку. Все mhash-функции устарели с8.1, MHASH-константы с8.5. Этот код нужен для изучения/переноса существующих данных, новые проекты используют Hash/password API. Сбой чтения или чужое предупреждение не маскируются.

Перевод строки меняет подписанные байты. В webhook проверяют исходное тело до повторного json_encode; защищённое сравнение не заменяет проверку времени/повтора, TLS и защиту ключа. Здесь только локальная вымышленная подпись. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Сохранить подпись старого интеграционного сообщенияСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $body = '{"order":42}';
    $key = 'demo-key-not-a-real-secret';
    $legacy = mhash(MHASH_SHA256, $body, $key);
    $modern = hash_hmac('sha256', $body, $key, true);
    var_dump(hash_equals($legacy, $modern));
    var_dump(base64_encode($legacy) === base64_encode($modern));
    var_dump(hash_equals($modern, hash_hmac('sha256', $body . "\n", $key, true)));
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
bool(true)
bool(true)
bool(false)
Устаревший API: да

Пустая строка всё равно выбирает HMAC, но не является подходящим секретом. Не проверяйте key через truthy/falsy при написании адаптера: это может незаметно сменить операцию. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Различить обычный digest, пустой ключ и ошибку алгоритмаСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $plain = mhash(MHASH_SHA256, 'abc', null);
    $emptyKey = mhash(MHASH_SHA256, 'abc', '');
    var_dump($plain === hash('sha256', 'abc', true));
    var_dump($emptyKey === hash_hmac('sha256', 'abc', '', true));
    var_dump($plain === $emptyKey);
    var_dump(mhash(-1, 'abc'));
    try { mhash(MHASH_CRC32, 'abc', 'demo'); }
    catch (ValueError $error) { echo "Алгоритм не подходит для HMAC\n"; }
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
bool(true)
bool(true)
bool(false)
bool(false)
Алгоритм не подходит для HMAC
Устаревший API: да

Заменить функцию и заменить алгоритм — разные миграции. MD5→SHA-256 меняет результат; hash("md5", ...) лишь воспроизводит прежний MD5 через другой API. Пароли не сохраняют через этот быстрый digest: нужен отдельный password_* справочник.

Начало и границы лаборатории: переход со старого интерфейса.

Связанные функции: hash, hash_hmac, hash_equals, mhash_get_hash_name.

mhash_countПолучить верхнюю границу старых числовых ID для исследования наследованного каталога.

PHP 4+; устарела с PHP 8.1 · hash

function mhash_count(): int

Найти алгоритм в старой таблице с пропусками. Название count вводит в заблуждение: возвращается максимальный ID, поэтому цикл включает саму границу через <=. mhash_get_hash_name может вернуть false для пустой позиции.

Путь конкретных данных
  1. Задан старый формат SHA256 и учебные байты
  2. Вызов mhash_count проверяет конкретную часть совместимости
  3. Миграция сохраняет байты либо явно останавливается

Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.

Получить верхнюю границу старых числовых ID для исследования наследованного каталога.

Параметр / результатКонтракт
АргументыНет.
ВозвратНаибольший предусмотренный ID, не число существующих алгоритмов. Диапазон включает 0 и может содержать незанятые позиции.
Версия и диагностические сообщенияС PHP 8.1 E_DEPRECATED. В проверенной PHP 8.5.11 возвращает 41; это снимок реализации, не число, которое надо зашить в приложение.

Скобки пусты, потому что функции не требуется входное значение. <= включает последний ID: если написать только <, последний алгоритм потеряется. Переменная со счётчиком найденных имён выполняет другую работу, чем верхняя граница.

Найти алгоритм в старой таблице с пропускамиСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $found = false;
    for ($id = 0; $id <= mhash_count(); ++$id) {
        $name = mhash_get_hash_name($id);
        if ($name === 'SHA256') { $found = true; echo 'Найден старый формат: ', $name, " → hash('sha256', данные, true)\n"; }
    }
    if (!$found) { echo "Формат SHA256 отсутствует: импорт остановлен.\n"; }
} finally { restore_error_handler(); }
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
Найден старый формат: SHA256 → hash('sha256', данные, true)
Устаревший API: да

Обработчик предупреждений временно собирает только ожидаемые deprecation Mhash; finally восстанавливает обычную обработку. Все mhash-функции устарели с8.1, MHASH-константы с8.5. Этот код нужен для изучения/переноса существующих данных, новые проекты используют Hash/password API. Сбой чтения или чужое предупреждение не маскируются.

Современный hash_algos сразу возвращает имена, но его набор не обязан совпадать с исторической таблицей Mhash. Это не равнозначные способы получить количество «всех криптографических алгоритмов». Некоторые элементы вообще не криптографические. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Посчитать реальные записи и обнаружить пропускиСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $lastId = mhash_count();
    $names = [];
    $gaps = [];
    for ($id = 0; $id <= $lastId; ++$id) {
        $name = mhash_get_hash_name($id);
        if ($name === false) { $gaps[] = $id; continue; }
        $names[$id] = $name;
    }
    echo count($names), "\n";
    echo implode(',', $gaps), "\n";
    echo in_array('sha256', hash_algos(), true) ? "SHA-256 доступен в Hash\n" : "Нет SHA-256\n";
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
39
4,6,26
SHA-256 доступен в Hash
Устаревший API: да

Начало и границы лаборатории: переход со старого интерфейса.

Связанные функции: mhash_get_hash_name, hash_algos, hash_hmac_algos.

mhash_get_block_sizeУзнать длину бинарного результата старого алгоритма, например для чтения сохранённого формата.

PHP 4+; устарела с PHP 8.1 · hash

function mhash_get_block_size(int $algo): int|false

Отклонить обрезанное поле старого пакета. Несмотря на имя, функция возвращает размер digest в байтах, а не внутренний размер блока алгоритма. У SHA256 это32. Длина подтверждает форму поля, не подлинность его содержимого.

Путь конкретных данных
  1. Задан старый формат SHA256 и учебные байты
  2. Вызов mhash_get_block_size проверяет конкретную часть совместимости
  3. Миграция сохраняет байты либо явно останавливается

Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.

Узнать длину бинарного результата старого алгоритма, например для чтения сохранённого формата.

Параметр / результатКонтракт
algoЧисловой идентификатор MHASH_*.
ВозвратЧисло байтов digest; неизвестный/незанятый ID — false. В PHP 8.5 реализация читает digest_size, несмотря название block_size.
Что это не означаетНе длина блока, который алгоритм обрабатывает внутри, не размер ключа HMAC, не рекомендуемая длина пароля.
Ошибки и версииНеверный тип при strict_types — TypeError; вызов устарел с PHP 8.1. Обрабатывайте false до арифметики и чтения байтов.

Размер помогает разобрать формат сообщения, но одинаковая длина двух строк не доказывает одинаковое содержимое. Для сравнения криптографической подписи нужен hash_equals. У SHA-256 размер digest — 32 байта, тогда как внутренний блок алгоритма — 64 байта: это разные величины.

Отклонить обрезанное поле старого пакетаСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $size = mhash_get_block_size(MHASH_SHA256);
    if ($size === false) { throw new RuntimeException('Алгоритм неизвестен'); }
    $full = mhash(MHASH_SHA256, 'invoice=42');
    $short = substr($full, 0, -1);
    echo strlen($full) === $size ? "Полный пакет: поле SHA256 можно читать.\n" : "Полный пакет: ошибка.\n";
    echo strlen($short) === $size ? "Короткий пакет принят.\n" : "Короткий пакет: импорт остановлен, не хватает байта.\n";
} finally { restore_error_handler(); }
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
Полный пакет: поле SHA256 можно читать.
Короткий пакет: импорт остановлен, не хватает байта.
Устаревший API: да

Обработчик предупреждений временно собирает только ожидаемые deprecation Mhash; finally восстанавливает обычную обработку. Все mhash-функции устарели с8.1, MHASH-константы с8.5. Этот код нужен для изучения/переноса существующих данных, новые проекты используют Hash/password API. Сбой чтения или чужое предупреждение не маскируются.

Это структурная проверка собственной fixture. Подлинность файла требует отдельной доверенной подписи/канала; атакующий легко создаст строку правильной длины. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Проверить полноту двоичного поля старого экспортаСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $expectedBytes = mhash_get_block_size(MHASH_SHA256);
    if ($expectedBytes === false) { throw new RuntimeException('Unknown format algorithm'); }
    $raw = mhash(MHASH_SHA256, 'record=42');
    $short = substr($raw, 0, -1);
    echo strlen($raw) === $expectedBytes ? "Поле полное\n" : "Поле обрезано\n";
    echo strlen($short) === $expectedBytes ? "Поле полное\n" : "Поле обрезано\n";
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
Поле полное
Поле обрезано
Устаревший API: да

Начало и границы лаборатории: переход со старого интерфейса.

Связанные функции: mhash, hash, mhash_get_hash_name, hash_equals.

mhash_get_hash_nameПрочитать историческое имя по ID, чтобы понять старую настройку и построить явное соответствие Hash.

PHP 4+; устарела с PHP 8.1 · hash

function mhash_get_hash_name(int $algo): string|false

Перевести известный старый ID в явную политику. Функция возвращает историческое имя по ID. strtolower не является универсальным преобразованием: TIGER/HAVAL имеют варианты, указанные в таблице ниже. Для SHA256 выбираем соответствие явно.

Путь конкретных данных
  1. Задан старый формат SHA256 и учебные байты
  2. Вызов mhash_get_hash_name проверяет конкретную часть совместимости
  3. Миграция сохраняет байты либо явно останавливается

Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.

Прочитать историческое имя по ID, чтобы понять старую настройку и построить явное соответствие Hash.

Параметр / результатКонтракт
algoID из старого формата или MHASH_*.
ВозвратИмя в историческом каталоге, например SHA256 или TIGER, либо false для отсутствующего ID. Это имя не обязано напрямую приниматься hash().
Ошибки и версииНеверный тип при strict_types — TypeError. E_DEPRECATED с PHP 8.1; константы MHASH_* тоже устарели с 8.5.

Удобно показать оператору читаемое имя, а затем проверить mapping. Привести имя к нижнему регистру недостаточно: TIGER соответствует tiger192,3, HAVAL256 — haval256,3. Числа относятся к варианту алгоритма; их потеря меняет контракт.

Перевести известный старый ID в явную политикуСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $legacyName = mhash_get_hash_name(MHASH_SHA256);
    if ($legacyName !== 'SHA256') { throw new RuntimeException('Неизвестный формат'); }
    echo $legacyName, " → sha256\n";
    echo 'Отпечаток invoice=42: ', hash('sha256', 'invoice=42'), "\n";
} finally { restore_error_handler(); }
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
SHA256 → sha256
Отпечаток invoice=42: 75bc7941966514f9788ff7c2126ee3384aeb0039bd7934b543aa7d3dc58875b7
Устаревший API: да

Обработчик предупреждений временно собирает только ожидаемые deprecation Mhash; finally восстанавливает обычную обработку. Все mhash-функции устарели с8.1, MHASH-константы с8.5. Этот код нужен для изучения/переноса существующих данных, новые проекты используют Hash/password API. Сбой чтения или чужое предупреждение не маскируются.

Полная таблица вариантов приведена в разделе констант. Мы проверяем совместимость записи старого приложения, а не выбираем эти алгоритмы для новой защиты. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Проверить точную замену TIGER и HAVALСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $variants = [[MHASH_TIGER, 'tiger192,3'], [MHASH_HAVAL256, 'haval256,3']];
    foreach ($variants as [$oldId, $newName]) {
        $oldResult = mhash($oldId, 'demo fixture');
        echo mhash_get_hash_name($oldId), ' -> ', $newName, ': ';
        echo $oldResult === hash($newName, 'demo fixture', true) ? "совпало\n" : "различается\n";
    }
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
TIGER -> tiger192,3: совпало
HAVAL256 -> haval256,3: совпало
Устаревший API: да

Начало и границы лаборатории: переход со старого интерфейса.

Связанные функции: mhash_count, mhash, hash_algos, hash.

mhash_keygen_s2kВоспроизвести ключ исторического Salted S2K формата при чтении старых данных.

PHP 4.0.4+; устарела с PHP 8.1 · hash

function mhash_keygen_s2k(int $algo, string $password, string $salt, int $length): string|false

Восстановить точные байты старого S2K перед миграцией. Старый формат использует соль длиной8байтов и особое построение блоков. Это не PBKDF2. Воспроизводимый тест показывает ключ открытого учебного вектора; настоящие ключи так не печатают.

Путь конкретных данных
  1. Задан старый формат SHA256 и учебные байты
  2. Вызов mhash_keygen_s2k проверяет конкретную часть совместимости
  3. Миграция сохраняет байты либо явно останавливается

Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.

Воспроизвести ключ исторического Salted S2K формата при чтении старых данных.

Параметр / результатКонтракт
algoЧисловой MHASH_* ID. Неизвестный ID возвращает false, если остальные проверки входа пройдены.
passwordИсходные байты старой парольной фразы. Метод преобразует их в ключ заданного формата; не шифрует данные и не является password_hash.
saltИсторическая соль ровно 8 байтов: короткая дополняется NUL справа, длинная обрезается до первых 8 байтов. Для восстановления нужен исходный договор хранения соли; соль не секрет.
lengthЗапрошенная длина ключа в байтах. В PHP 8.5 значение 0 или отрицательное вызывает ValueError. Ограничивайте длину доверенным форматом; это не произвольный размер из HTTP.
Возврат и ошибкиБинарная строка указанной длины либо false для неизвестного ID; неверные типы при strict_types — TypeError. Полученный ключ секретен: не выводить и не складывать рядом с защищаемыми данными.
ВерсииС PHP 4.0.4; устарела с 8.1. Современные KDF имеют другой формат и параметры, поэтому замена одним именем обычно несовместима.

S2K означает string-to-key — преобразование строки в ключ. Это Salted S2K из старого OpenPGP RFC 2440, без настраиваемого количества итераций. Более длинный результат не увеличивает непредсказуемость слабого пароля до длины ключа. Не используйте эту функцию для новых паролей аккаунтов или нового формата шифрования.

Восстановить точные байты старого S2K перед миграциейСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $key = mhash_keygen_s2k(MHASH_SHA256, 'legacy phrase', '12345678', 16);
    if ($key === false) { throw new RuntimeException('Старый ключ не получен'); }
    echo 'Учебный S2K: ', bin2hex($key), "\n";
    $different = hash_pbkdf2('sha256', 'legacy phrase', '12345678', 2, 16, true);
    echo hash_equals($key, $different) ? "Можно заменить без смены формата.\n" : "PBKDF2 даёт другой ключ; нужна миграция формата.\n";
} finally { restore_error_handler(); }
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
Учебный S2K: 98c2114895f7b2d04475053341fb14f9
PBKDF2 даёт другой ключ; нужна миграция формата.
Устаревший API: да

Обработчик предупреждений временно собирает только ожидаемые deprecation Mhash; finally восстанавливает обычную обработку. Все mhash-функции устарели с8.1, MHASH-константы с8.5. Этот код нужен для изучения/переноса существующих данных, новые проекты используют Hash/password API. Сбой чтения или чужое предупреждение не маскируются.

Длинная соль не добавляет защиту этому API после восьмого байта. При переносе старых данных необходимо сохранить именно его правило, а не подставить полный salt в другую функцию. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Увидеть усечение и дополнение солиСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $base = mhash_keygen_s2k(MHASH_SHA256, 'demo', '12345678', 16);
    $long = mhash_keygen_s2k(MHASH_SHA256, 'demo', '12345678EXTRA', 16);
    $short = mhash_keygen_s2k(MHASH_SHA256, 'demo', 'abc', 16);
    $padded = mhash_keygen_s2k(MHASH_SHA256, 'demo', "abc\0\0\0\0\0", 16);
    var_dump(hash_equals($base, $long));
    var_dump(hash_equals($short, $padded));
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
bool(true)
bool(true)
Устаревший API: да

Здесь SHA-1 и одна итерация PBKDF2 — только сравнение старых формул, не рекомендуемая политика. Salted S2K для дополнительных блоков добавляет 0, 1, 2… NUL перед солью/паролем, объединяет digest и обрезает до length. Это не последовательное «хешировать предыдущий блок» и не PBKDF2. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Понять второй digest и отличие PBKDF2Самостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    $salt = '12345678';
    $password = 'demo legacy phrase';
    $legacy = mhash_keygen_s2k(MHASH_SHA1, $password, $salt, 40);
    $firstBlock = hash('sha1', $salt . $password, true);
    $secondBlock = hash('sha1', "\0" . $salt . $password, true);
    var_dump(hash_equals($legacy, $firstBlock . $secondBlock));
    $different = hash_pbkdf2('sha1', $password, $salt, 1, 40, true);
    var_dump(hash_equals($legacy, $different));
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
bool(true)
bool(false)
Устаревший API: да

Параметры архива проверяются до выделения памяти. PHP 8.5 внутри использует C int для длины; технические пределы не должны становиться политикой обработки недоверенного ввода. Огромные размеры в этих опытах не выделяются. Обработчик учитывает только ожидаемые E_DEPRECATED этого семейства, сообщает о них последней строкой и восстанавливает прежний обработчик. Другие ошибки не скрываются; это лаборатория совместимости, не настройка журналирования сервера.

Проверить отказ до чтения старого архиваСамостоятельный пример
<?php
declare(strict_types=1);

$legacyNotice = false;
set_error_handler(function (int $level, string $message) use (&$legacyNotice): bool {
    if ($level === E_DEPRECATED
        && (str_starts_with($message, 'Function mhash') || str_starts_with($message, 'Constant MHASH_'))) {
        $legacyNotice = true;
        return true;
    }
    return false;
});
try {
    var_dump(mhash_keygen_s2k(-1, 'demo', '12345678', 16));
    foreach ([0, -1] as $length) {
        try { mhash_keygen_s2k(MHASH_SHA256, 'demo', '12345678', $length); }
        catch (ValueError $error) { echo "Неверная длина\n"; }
    }
} finally {
    restore_error_handler();
}
echo $legacyNotice ? "Устаревший API: да\n" : "Устаревший API: нет\n";
Ожидаемый результат
bool(false)
Неверная длина
Неверная длина
Устаревший API: да

Для нового случайного ключа используют криптографический генератор, для ключа из пароля — согласованную современную KDF, для хранения пароля — password_hash. hash_pbkdf2 и hash_hkdf не являются побайтовыми заменами S2K, а HKDF не превращает слабый пароль в стойкий секрет. Миграция зашифрованного архива требует прочитать его старым форматом и записать новым с отдельной версией, солью/nonce и аутентификацией; эта глава не реализует шифрование.

Начало и границы лаборатории: переход со старого интерфейса.

Связанные функции: hash_pbkdf2, hash_hkdf, hash_equals, mhash.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.