Глава 81 · Практика
Hash: отпечатки файлов, HMAC и производные ключи
Полный справочник hash: от проверки выгрузки до подписи webhook, потокового HashContext и точного выбора двоичных форматов.
Заметить изменение файла или текста
Одна изменённая цифра — проверяем другие байты
Сравниваем доверенный исходник с полученной строкой. Обе стороны используют один алгоритм и одно представление.
- Исходник: order=42
Доверенные байты сохраняются как основание проверки.
- hash('sha256', $original)
Вычисление получает имя алгоритма и ровно эти байты.
- $saved: 64 hex-знака
Это 32 байта результата, записанные двумя знаками на каждый байт.
- Получено: order=43
Последняя цифра другая; значение переменной $original не меняется.
- hash('sha256', $received)
Тот же алгоритм обрабатывает полученные байты.
- $saved === результат → false
В этом примере отпечатки различаются: программа выводит changed.
Доверие к исходной сумме обязательно. Обычный hash не содержит секретного ключа и не доказывает подлинность отправителя; для этой задачи далее разбирается HMAC.
Вы отправили поставщику выгрузку и хотите проверить, что у него те же байты. Хеш — короткий результат вычисления по содержимому. При одинаковом алгоритме и одинаковых байтах результат повторяется. Сначала проверим это на строке: файл тоже представляет собой последовательность байтов.
- Сохранили доверенный отпечаток исходного текста
- hash получает имя sha256 и проверяемые байты
- Алгоритм возвращает строку из 64 hex-знаков
- Сравниваем строки: изменённый текст даёт другой отпечаток
$original — переменная с исходной строкой; = сохраняет значение. hash — имя функции, а два аргумента в скобках задают алгоритм и данные. Кавычки обозначают текст. === проверяет точное совпадение значений и типов. Тернарная запись условие ? одно : другое выбирает сообщение; echo печатает его. Здесь результат сравнения открытых контрольных сумм не является секретом.
Для первого чтения: переменные, аргументы и возвращаемое значение. ООП и собственный класс пока не нужны: одна готовая функция решает задачу.
<?php
declare(strict_types=1);
$text = 'order=42';
$checksum = hash('sha256', $text);
echo 'order=42: ', $checksum, "\n";
Ожидаемый результат
order=42: e7be97a0dece7cd8751b91338ce03d916b01ee32616d2b8b70e1807e5f307705
Одинаковые байты и алгоритм дают эту же строку. В карточках ниже сравниваем изменённую запись, приложенный файл и сообщение с HMAC. Контрольная сумма без доверенного источника не подтверждает отправителя.
Хеширует байты, а возвращает hex или двоичную строку
SHA-256 возвращает 256 бит, то есть 32 байта. По умолчанию PHP записывает каждый байт двумя шестнадцатеричными знаками: всего 64. binary=true оставляет 32 исходных байта, среди которых могут быть NUL и непечатаемые значения. Это всё ещё PHP string. Для показа используют bin2hex; для протокола выбирают ровно требуемое им представление, например Base64 от двоичного результата.
<?php
declare(strict_types=1);
$hex = hash('sha256', 'abc');
$raw = hash('sha256', 'abc', true);
echo strlen($hex), '/', strlen($raw), "\n";
var_dump($hex === bin2hex($raw));
Ожидаемый результат
64/32
bool(true)
true — третий аргумент, он меняет только представление. Это не дополнительный уровень защиты. Передавать бинарную строку в JSON без согласованного кодирования нельзя.
<?php
declare(strict_types=1);
$a = '{"id":42}';
$b = '{"id": 42}';
var_dump(json_decode($a, true) === json_decode($b, true));
var_dump(hash('sha256', $a) === hash('sha256', $b));
Ожидаемый результат
bool(true)
bool(false)
Хеш не понимает смысл JSON. Аналогично важны LF/CRLF, BOM, кодировка, порядок полей и Unicode-нормализация. Если протокол подписывает raw body, его проверяют до decode/re-encode; если требует канонизацию, обе стороны реализуют именно её спецификацию.
Контрольная сумма, HMAC, пароль и шифрование
| Потребность | Подход | Граница |
|---|---|---|
| Выявить случайное повреждение | CRC/adler или согласованный SHA-256 | CRC легко подделать; доверие не возникает из самой суммы. |
| Индекс/распределение в таблице | Некриптографические xxHash/Murmur | Коллизии возможны; храните исходный ключ для проверки. |
| Сверить содержимое по доверенному манифесту | hash_file с SHA-256 | MD5/SHA-1 не выбирают для защиты от намеренной подмены. |
| Проверить сообщение от обладателя общего секрета | hash_hmac и hash_equals | Не шифрует и не защищает от повторной доставки сам по себе. |
| Сохранить пароль пользователя | password_hash / password_verify | Быстрый hash, даже SHA-256 с солью, не заменяет парольный алгоритм. |
| Получить отдельные ключи для разных задач | hash_hkdf | Требует качественный исходный ключ; не делает слабый пароль сильным. |
| Воспроизвести PBKDF2-контракт внешнего формата | hash_pbkdf2 | Параметры и дорогое вычисление согласуют отдельно; для обычного входа проще password_hash. |
| Скрыть содержимое и потом восстановить его | Аутентифицированное шифрование из Sodium/OpenSSL | Hash не является обратимым шифрованием; HMAC не цифровая подпись с открытым ключом. |
Хеши фиксированной длины неизбежно имеют коллизии. Криптографический алгоритм должен затруднять практически полезный поиск подмены, а не обещать математическую уникальность любых данных. Публичный хеш короткого значения, например номера телефона, допускает перебор кандидатов и не является анонимизацией. HMAC тоже не даёт неотказуемость: общий секрет известен обеим сторонам.
Что установлено и какие настройки существуют
С PHP 7.4 расширение hash входит в обязательную часть PHP. Раньше его можно было отключить при сборке через --disable-hash; до 5.1.2 оно распространялось через PECL. Специальных INI-настроек у этой книги нет. Алгоритм, режим и ключ передаются в конкретный вызов; проверить окружение помогают php --ri hash и списки алгоритмов.
<?php
declare(strict_types=1);
echo extension_loaded('hash') ? 'hash: yes' : 'hash: no';
echo "\n";
$context = hash_init('sha256');
echo get_debug_type($context), "\n";
var_dump(is_resource($context));
Ожидаемый результат
hash: yes
HashContext
bool(false)
В PHP 7.2+ фабрика возвращает объект HashContext. Старая официальная страница Resource Types описывает предыдущий resource-контракт; переносить эту проверку в современный код нельзя.
HASH_HMAC — целочисленный флаг для hash_init. Передать ключ третьим аргументом при flags=0 недостаточно: HMAC включается именно флагом. Допустимый прикладной выбор — 0 либо HASH_HMAC; произвольные биты не являются документированными режимами. Сам ключ хранится в конфигурации/хранилище секретов, а не приходит из тела подписываемого запроса.
Алгоритмы и их options: выбираем явно
| Семейство | Имена из современного списка |
|---|---|
| SHA | sha1; sha224, sha256, sha384, sha512/224, sha512/256, sha512; sha3-224, sha3-256, sha3-384, sha3-512 |
| Исторические/специализированные | md2, md4, md5; ripemd128/160/256/320; whirlpool; tiger128,3 / tiger160,3 / tiger192,3 и варианты ,4; snefru, snefru256; gost, gost-crypto; haval128/160/192/224/256 с ,3 / ,4 / ,5 |
| Некриптографические | adler32; crc32, crc32b, crc32c; fnv132, fnv1a32, fnv164, fnv1a64; joaat; murmur3a, murmur3c, murmur3f; xxh32, xxh64, xxh3, xxh128 |
Наличие имени в списке — совместимость реализации, а не рекомендация безопасности. SHA-3 и усечённые SHA-512 появились в 7.1; crc32c — в 7.4; MurmurHash3 и xxHash — в 8.1. Не заменяйте алгоритм молча на «первый доступный»: другая сторона получит другой результат.
| options в PHP 8.5.11 | Правило |
|---|---|
| MurmurHash3 | seed — int; при отсутствии 0. Для переносимых учебных вызовов берём небольшое неотрицательное целое. |
| xxh32 / xxh64 | Также поддерживают int seed. seed меняет распределение, но не превращает алгоритм в HMAC. |
| xxh3 / xxh128 | int seed либо string secret; одновременно нельзя. secret минимум 136 байтов, максимум используемой части 256; более длинный вызывает warning и обрезку. Имя secret не делает xxHash криптографическим. |
| SHA-256 и другие алгоритмы без таких настроек | Передавайте []. Неизвестные options могут игнорироваться, поэтому массив не является универсальным валидатором параметров. |
Общие страницы пока перечисляют только Murmur seed. Дополнение про xxHash сверено с исходниками PHP 8.5.11. В PHP 8.4+ неверные типы seed/secret deprecated; их нельзя использовать как способ преобразования строковых настроек. Для xxh3/xxh128 короткий secret и одновременные seed+secret дают Error. Значения параметров, влияющих на результат, сохраняют вместе с выбранным алгоритмом.
<?php
declare(strict_types=1);
$options = ['seed' => 7];
$direct = hash('murmur3a', 'product:42', false, $options);
$context = hash_init('murmur3a', 0, '', $options);
hash_update($context, 'product:42');
var_dump($direct === hash_final($context));
var_dump($direct !== hash('murmur3a', 'product:42', false, ['seed' => 8]));
Ожидаемый результат
bool(true)
bool(true)
Это детерминированное распределение ключа, не подпись заказа. Для HMAC требуется криптографический алгоритм и ключ в отдельном параметре.
Большой файл: накопить части, затем завершить
- hash_init создаёт отдельное состояние алгоритма
- hash_update добавляет очередные байты в прежнее состояние
- hash_copy ответвляет независимую копию, если нужен промежуточный результат
- hash_final выдаёт отпечаток и закрывает именно этот контекст
HashContext хранит состояние вычисления и небольшой рабочий буфер, а не коллекцию всех переданных строк. При этом hash($algorithm, file_get_contents(...)) сначала создаёт большую строку вне алгоритма. Для одного файла подходит hash_file; для уже открытого потока — hash_update_stream. Все байты всё равно должны быть прочитаны: время зависит от объёма, диска/потока, алгоритма и оборудования. Нельзя обещать постоянное время чтения большого файла или переносить одно измерение на другой сервер.
Проверка содержимого и последующее чтение по тому же пути — две операции: файл между ними может измениться. Для недоверенного загрузочного процесса сначала фиксируют файл в контролируемом хранилище и применяют свою политику доступа. Пользовательский URL как filename может обратиться во внешнюю сеть или локальные wrapper-ресурсы; примеры ниже используют только собственные локальные fixtures.
HashContext: объект состояния, а не набор готовых хешей
Для обычной строки достаточно hash; объект нужен, когда текст поступает частями. HashContext — внутренний final-класс PHP. Его создаёт hash_init, а операции update/final принимают именно этот объект. Отдельный PHP-объект не переживает новый HTTP-запрос сам по себе.
- hash_init создаёт контекст A
- $other = $context указывает на тот же A
- hash_copy создаёт независимый B с тем же префиксом
- final(A) закрывает A; B можно продолжить
Не путайте внутренний HashContext со stream context: последний настраивает чтение/запись, первый накапливает криптографическое вычисление. Простой assignment не копирует объект; поэтому изменение через одну переменную видно через другую. Классы и стрелка метода разобраны в основах объектов.
| Механизм | Когда нужен |
|---|---|
| Конструктор | Закрыт; используем фабрику hash_init. |
| Копия | hash_copy для ответвления или промежуточного digest. |
| Диагностика 8.4+ | __debugInfo показывает algo, не раскрывает полный рабочий буфер. |
| Сериализация 8.0+ | Только доверенные поддерживаемые снимки; HMAC запрещён, переносимость не гарантирована. |
Справочник и упражнение на выбор механизма
| Функция / метод | Задача |
|---|---|
hash | Получить воспроизводимый отпечаток уже имеющейся строки. |
hash_algos | Узнать алгоритмы, реально зарегистрированные в текущем PHP. |
hash_copy | Ответвить независимое вычисление после общего префикса. |
hash_equals | Сравнить проверяемую секретную строку без раннего выхода по первому отличающемуся байту. |
hash_file | Прочитать локальный файл потоково и вычислить отпечаток содержимого. |
hash_final | Получить окончательный отпечаток и завершить конкретный контекст. |
hash_hkdf | Получить отдельный ключ для конкретной роли из исходного ключевого материала. |
hash_hmac | Вычислить код аутентификации сообщения с общим секретом. |
hash_hmac_algos | Получить список алгоритмов, для которых PHP реализует HMAC. |
hash_hmac_file | Вычислить MAC содержимого файла с известным обеим сторонам ключом. |
hash_init | Создать состояние вычисления, в которое данные будут поступать частями. |
hash_pbkdf2 | Воспроизвести согласованное PBKDF2-выведение ключа из пароля. |
hash_update | Добавить очередную строку байтов к активному вычислению. |
hash_update_file | Добавить весь файл к уже накопленному префиксу вычисления. |
hash_update_stream | Добавить байты из уже открытого потока, начиная с его текущей позиции. |
HashContext::__construct | Понять, почему контекст создают hash_init, а не через new. |
HashContext::__debugInfo | Увидеть алгоритм контекста в диагностическом выводе. |
HashContext::__serialize | Сохранить поддерживаемое активное состояние обычного хеширования. |
HashContext::__unserialize | Восстановить собственный доверенный снимок HashContext для продолжения вычисления. |
Упражнение. Для архива экспорта есть доверенный SHA-256 манифест; от платёжного сервиса приходит JSON с HMAC-SHA-256 в Base64; пользователь вводит пароль в форму. Выберите три разные проверки. Что поменяется, если JSON пересохранить с отступами? Что произойдёт, если повторить уже правильно подписанное событие?
Разбор. Архив читаем через hash_file и сравниваем с доверенным манифестом. Webhook проверяем по точным согласованным байтам через hash_hmac с binary=true, Base64 и hash_equals; дополнительно по протоколу проверяем подписанную свежесть и атомарно учитываем event-id в постоянном хранилище. Пароль проверяем password_verify по результату password_hash. Переформатирование JSON меняет байты и подпись; повторная доставка сохраняет корректную подпись, поэтому сама криптография её не отвергает. Примеры hash_hmac и hash_equals ниже позволяют воспроизвести первый сбой локально.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
hashПолучить воспроизводимый отпечаток уже имеющейся строки.
hash(string $algo, string $data, bool $binary = false, array $options = []): stringЗаметить изменение номера заказа. В hash('sha256', $text) первый аргумент выбирает алгоритм, второй задаёт точные байты. Возвращается строка отпечатка; исходный текст не меняется. === сравнивает и значение, и тип.
- order=42 → sha256 → доверенный отпечаток
- Получено order=43 → новый отпечаток
- Сравнение замечает изменение и останавливает импорт
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Получить воспроизводимый отпечаток уже имеющейся строки.
| Параметр / результат | Контракт |
|---|---|
| algo | Имя из hash_algos, выбранное по контракту задачи; например sha256. |
| data | Точные байты сообщения, включая пробелы, NUL и переводы строк. Пустая строка допустима. |
| binary | false — нижний hex; true — исходные двоичные байты. |
| options | Настройки выбранного алгоритма; [] для SHA-256. seed/secret разобраны в разделе options. |
| return | Новая строка с отпечатком; исходная data не меняется. Её длина зависит от алгоритма и binary. |
<?php
declare(strict_types=1);
$original = 'order=42';
$trusted = hash('sha256', $original);
echo 'Отпечаток order=42: ', $trusted, "\n";
$received = 'order=43';
echo $trusted === hash('sha256', $received) ? "Заказ не изменён.\n" : "Заказ изменён; импорт остановлен.\n";
Ожидаемый результат
Отпечаток order=42: e7be97a0dece7cd8751b91338ce03d916b01ee32616d2b8b70e1807e5f307705
Заказ изменён; импорт остановлен.
Доверенный отпечаток должен храниться отдельно от недоверенной доставки. Если посторонний может заменить и файл, и его сумму, SHA-256 сам не подтвердит отправителя; для общего секрета нужен HMAC, для паролей — password_hash.
Версия преобразования включена в ключ, иначе изменение настроек оставит прежний кэш. Фиксированный namespace с разделителем и фиксированным hex-хвостом однозначен. Для нескольких произвольных полей нельзя просто склеить строки: пары ab+c и a+bc совпадут; сначала задают однозначное кодирование полей.
<?php
declare(strict_types=1);
$content = 'demo image bytes';
$pipeline = 'thumbnail-v2';
$key = $pipeline . ':' . hash('sha256', $content);
$same = $pipeline . ':' . hash('sha256', $content);
$next = 'thumbnail-v3:' . hash('sha256', $content);
echo str_starts_with($key, 'thumbnail-v2:') ? 'versioned' : 'bad';
echo "\n";
var_dump($key === $same, $key !== $next);
Ожидаемый результат
versioned
bool(true)
bool(true)
catch здесь показывает контракт ошибки. В приложении исправляют доверенную конфигурацию, а не подставляют пустой хеш и не принимают непроверенный алгоритм от клиента.
<?php
declare(strict_types=1);
try {
hash('sha-unknown', 'abc');
} catch (ValueError $error) {
echo 'invalid algorithm';
}
Ожидаемый результат
invalid algorithmОшибки и ограничения. Неизвестный algo вызывает ValueError с PHP 8; раньше были warning и false. options добавлен в 8.1, неверные типы настроек некоторых алгоритмов deprecated с 8.4. См. точные правила options. Быстрый digest не заменяет парольный алгоритм и не скрывает содержание.
Продолжить: hash_file, hash_init, hash_hmac. Как выбрать задачу и алгоритм.
hash_algosУзнать алгоритмы, реально зарегистрированные в текущем PHP.
hash_algos(): arrayПроверить алгоритм перед проверкой экспорта. Список hash_algos отражает возможности текущего процесса, включая алгоритмы, которые не предназначены для защиты. Наличие имени не означает, что его разрешила политика сервиса. Выбираем sha256 явно и отказываем при отсутствии.
- Проект требует sha256
- hash_algos перечисляет доступные имена
- Проверенное имя применяется к order=42
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Узнать алгоритмы, реально зарегистрированные в текущем PHP.
| Параметр / результат | Контракт |
|---|---|
| Параметры | Нет: результат описывает расширение в этом процессе, а не удалённый сервер. |
| return | Индексированный массив строк — имён алгоритмов. Наличие имени не является оценкой его безопасности. |
<?php
declare(strict_types=1);
if (!in_array('sha256', hash_algos(), true)) { throw new RuntimeException('Нет обязательного sha256'); }
echo "Проверка экспорта: sha256 доступен.\n";
echo 'Контрольный результат: ', hash('sha256', 'order=42'), "\n";
Ожидаемый результат
Проверка экспорта: sha256 доступен.
Контрольный результат: e7be97a0dece7cd8751b91338ce03d916b01ee32616d2b8b70e1807e5f307705
Фиксированный ключ здесь только для известного тестового вектора; настоящие ключи хранят закрыто и генерируют случайно. Алгоритм из запроса нельзя принимать только потому, что он есть в этом списке.
Расширение может поддерживать устаревший алгоритм для совместимости. Политика сервиса определяется отдельно; переключение алгоритма, если оно вообще нужно протоколу, должно входить в защищённые параметры.
<?php
declare(strict_types=1);
$allowed = ['sha256', 'sha512'];
$available = hash_algos();
foreach (['sha256', 'md5'] as $requested) {
$ok = in_array($requested, $allowed, true)
&& in_array($requested, $available, true);
echo $requested, ':', $ok ? 'allowed' : 'rejected', "\n";
}
Ожидаемый результат
sha256:allowed
md5:rejected
Ошибки и ограничения. Нет параметров конфигурации и специального значения false. Ветки SHA-3/sha512 truncation добавлены в 7.1, crc32c в 7.4, Murmur/xxHash в 8.1. Список алгоритмов текущей версии перечислен в таблице семейств.
Продолжить: hash_hmac_algos, hash. Как выбрать задачу и алгоритм.
hash_fileПрочитать локальный файл потоково и вычислить отпечаток содержимого.
hash_file(string $algo, string $filename, bool $binary = false, array $options = []): string|falseПроверить содержимое полученной выгрузки. Файл export.txt приложен: две строки order=42 и qty=2, каждая заканчивается LF. hash_file читает его содержимое, не имя. Путь __DIR__ задаёт каталог example.php.
- export.txt: order=42 LF qty=2 LF
- hash_file читает все15байтов файла
- SHA256 сравнивается с заранее заданным манифестом
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Прочитать локальный файл потоково и вычислить отпечаток содержимого.
| Параметр / результат | Контракт |
|---|---|
| algo | Поддерживаемый алгоритм, например sha256. |
| filename | Путь или поддерживаемый fopen wrapper. Хешируется содержимое, не имя файла и не его время изменения. |
| binary | false — нижний hex; true — двоичный результат. |
| options | Настройки алгоритма, добавлены в PHP 8.1; не stream context. |
| return | Digest string либо false при невозможности открыть/прочитать файл. false проверяют строго. |
<?php
declare(strict_types=1);
$expected = 'c061afdd8db7ecdf0b1ed01d735f3aebadc4d8f6e0d0cb42b6de4d41d3aad987';
$actual = hash_file('sha256', __DIR__ . '/export.txt');
if ($actual === false) { throw new RuntimeException('Выгрузка не прочитана'); }
echo 'export.txt: ', $actual, "\n";
echo hash_equals($expected, $actual) ? "Заказ42, количество2: файл соответствует манифесту.\n" : "Импорт запрещён: файл изменён.\n";
Ожидаемый результат
export.txt: c061afdd8db7ecdf0b1ed01d735f3aebadc4d8f6e0d0cb42b6de4d41d3aad987
Заказ42, количество2: файл соответствует манифесту.
Манифест в этом опыте задан доверенной константой в коде. Измените qty=2 на qty=9 и получите отказ. CRLF, BOM или дополнительный перевод строки тоже меняют байты и отпечаток.
Для дедупликации важен контент; имя, метаданные и права доступа нужно учитывать отдельно. Строгое сравнение здесь относится к открытым отпечаткам. Если это секретный MAC, выбирайте hash_equals.
<?php
declare(strict_types=1);
$left = hash_file('sha256', __DIR__ . '/first.csv', true);
$right = hash_file('sha256', __DIR__ . '/second.csv', true);
if ($left === false || $right === false) { throw new RuntimeException('Read failed'); }
echo $left === $right ? "first.csv и second.csv содержат одинаковые байты.\n" : "Содержимое CSV различается.\n";
Ожидаемый результат
first.csv и second.csv содержат одинаковые байты.
Вывод показывает результат описанного выше опыта с указанными исходными данными.
Только учебная проверка намеренно перехватывает ожидаемое warning. В рабочем процессе отказ чтения останавливает проверку; предупреждения не скрывают глобально и пути/данные не выводят клиенту.
<?php
declare(strict_types=1);
$warnings = 0;
set_error_handler(function (int $level) use (&$warnings): bool {
if ($level !== E_WARNING) { return false; }
++$warnings;
return true;
});
try { $result = hash_file('sha256', __DIR__ . '/absent.txt'); }
finally { restore_error_handler(); }
echo $result === false && $warnings > 0 ? "Импорт остановлен: файл не прочитан, предупреждение получено.\n" : "Отказ не обнаружен.\n";
Ожидаемый результат
Импорт остановлен: файл не прочитан, предупреждение получено.
Ошибки и ограничения. Неверный algo и NUL в пути дают ValueError; невозможность чтения — warning/false. Интерфейс работает с wrappers, поэтому filename из запроса создаёт отдельный риск доступа к файлам/сети. В PHP 8.5 реализация читает блоками; файловый кэш ОС при этом может расти. Потоковое чтение не гарантирует неизменность файла между проверкой и использованием.
Продолжить: hash, hash_update_file, hash_update_stream, hash_hmac_file. Как выбрать задачу и алгоритм.
hash_initСоздать состояние вычисления, в которое данные будут поступать частями.
hash_init(string $algo, int $flags = 0, string $key = "", array $options = []): HashContextПолучить отпечаток отчёта из двух строк. Создаёт пустое состояние вычисления. Переменная context хранит объект, который связывает следующие update и final. init сам не получает пользовательские байты.
- hash_init(sha256) создаёт пустой контекст
- update добавляет order=42 LF, затем qty=2 LF
- final возвращает отпечаток целого отчёта
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Создать состояние вычисления, в которое данные будут поступать частями.
| Параметр / результат | Контракт |
|---|---|
| algo | Имя алгоритма. Без HASH_HMAC разрешены и некриптографические алгоритмы. |
| flags | 0 — обычный digest; HASH_HMAC — режим с секретным ключом. |
| key | Непустой общий секрет, если flags включает HASH_HMAC. При flags=0 не включает HMAC и не участвует как такой ключ. |
| options | [] по умолчанию; настройки алгоритма, например целочисленный seed для Murmur. Не путать с flags. |
| return | Новый независимый HashContext. В нём ещё нет пользовательских данных: следующий шаг — update. |
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, "order=42\n");
hash_update($context, "qty=2\n");
$digest = hash_final($context);
echo 'Весь отчёт: ', $digest, "\n";
echo "Строки добавлены в порядке: order=42, затем qty=2.\n";
Ожидаемый результат
Весь отчёт: c061afdd8db7ecdf0b1ed01d735f3aebadc4d8f6e0d0cb42b6de4d41d3aad987
Строки добавлены в порядке: order=42, затем qty=2.
Конкатенация этих частей равна точному содержимому export.txt из соседней карточки. Это один хеш всего отчёта; отдельно хешировать строки и соединять их хеши означало бы другой алгоритм.
Ключ здесь — открытый fixture, использовать его в сервисе нельзя. Реальный секрет известен доверенным сторонам; при отсутствии конфигурации сервис должен отказать в проверке, а не работать с пустой строкой.
<?php
declare(strict_types=1);
$key = 'demo-only-key';
$context = hash_init('sha256', HASH_HMAC, $key);
hash_update($context, 'order:');
hash_update($context, '42');
var_dump(hash_final($context) === hash_hmac('sha256', 'order:42', $key));
try { hash_init('sha256', HASH_HMAC, ''); }
catch (ValueError $error) { echo 'missing key rejected'; }
Ожидаемый результат
bool(true)
missing key rejectedОшибки и ограничения. ValueError: неизвестный algo; некриптографический algo именно в HMAC-режиме; пустой ключ именно при HASH_HMAC. Обычный hash_init без ключа корректен. С 7.2 возвращается объект вместо resource; с 8.1 есть options; неверные типы некоторых options deprecated с 8.4. Отдельные правила xxHash смотрите в options.
Продолжить: hash_update, hash_final, hash_copy, hash_hmac. Как выбрать задачу и алгоритм.
hash_updateДобавить очередную строку байтов к активному вычислению.
hash_update(HashContext $context, string $data): trueПолучить отпечаток отчёта из двух строк. Добавляет следующую часть без разделителей. Поэтому LF пишем явно в строке. Результат update сообщает успех; окончательный digest возвращает отдельный final.
- Контекст sha256 уже создан
- Первый update: order=42 LF; второй: qty=2 LF
- final считает обе части в исходном порядке
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Добавить очередную строку байтов к активному вычислению.
| Параметр / результат | Контракт |
|---|---|
| context | Активный, ещё не финализированный HashContext из hash_init. |
| data | Следующая порция байтов; пустая строка допустима. Разделители автоматически не добавляются. |
| return | true при нормальном завершении. С PHP 8.4 это также точный объявленный return type вместо bool. |
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, "order=42\n");
hash_update($context, "qty=2\n");
$digest = hash_final($context);
echo 'Весь отчёт: ', $digest, "\n";
echo "Строки добавлены в порядке: order=42, затем qty=2.\n";
Ожидаемый результат
Весь отчёт: c061afdd8db7ecdf0b1ed01d735f3aebadc4d8f6e0d0cb42b6de4d41d3aad987
Строки добавлены в порядке: order=42, затем qty=2.
Конкатенация этих частей равна точному содержимому export.txt из соседней карточки. Это один хеш всего отчёта; отдельно хешировать строки и соединять их хеши означало бы другой алгоритм.
foreach берёт строки по порядку; здесь формат запрещает переводы строк внутри значения. Для произвольных полей нужен однозначный формат, например длина каждого поля перед его байтами. Hash не знает границы записей и не исправит неоднозначное склеивание.
<?php
declare(strict_types=1);
$records = ['order=42', 'qty=2'];
$context = hash_init('sha256');
foreach ($records as $record) {
hash_update($context, $record . "\n");
}
$digest = hash_final($context);
echo "Проверены строки order=42 и qty=2 с LF.\nОтпечаток: ", $digest, "\n";
Ожидаемый результат
Проверены строки order=42 и qty=2 с LF.
Отпечаток: c061afdd8db7ecdf0b1ed01d735f3aebadc4d8f6e0d0cb42b6de4d41d3aad987
Вывод показывает результат описанного выше опыта с указанными исходными данными.
Ошибки и ограничения. Неверный тип или уже финализированный context вызывает TypeError в PHP 8.5; true относится только к успешному вызову. С 7.2 параметр — HashContext. Порядок частей важен; хеш хешей отдельных блоков обычно не равен хешу исходного файла.
Продолжить: hash_init, hash_final, hash_update_file, hash_update_stream. Как выбрать задачу и алгоритм.
hash_finalПолучить окончательный отпечаток и завершить конкретный контекст.
hash_final(HashContext $context, bool $binary = false): stringПолучить отпечаток отчёта из двух строк. Получает окончательную строку и завершает состояние. Сохраните результат в переменной: повторный final/update того же контекста недопустим.
- Контекст накопил15байтов отчёта
- final возвращает digest и закрывает вычисление
- Повторно используем строку результата; для новых данных нужен новый контекст
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Получить окончательный отпечаток и завершить конкретный контекст.
| Параметр / результат | Контракт |
|---|---|
| context | Активный HashContext. После вызова хранится тот же объект, но продолжать вычисление им уже нельзя. |
| binary | false — нижний hex, true — исходные байты результата. |
| return | Строка digest/HMAC для всех ранее добавленных байтов в исходном порядке. |
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, "order=42\n");
hash_update($context, "qty=2\n");
$digest = hash_final($context);
echo 'Весь отчёт: ', $digest, "\n";
echo "Строки добавлены в порядке: order=42, затем qty=2.\n";
Ожидаемый результат
Весь отчёт: c061afdd8db7ecdf0b1ed01d735f3aebadc4d8f6e0d0cb42b6de4d41d3aad987
Строки добавлены в порядке: order=42, затем qty=2.
Конкатенация этих частей равна точному содержимому export.txt из соседней карточки. Это один хеш всего отчёта; отдельно хешировать строки и соединять их хеши означало бы другой алгоритм.
Сохраняйте возвращённую строку, если она нужна ещё раз. Для промежуточного просмотра делают hash_final(hash_copy($context)); исходное вычисление при этом остаётся активным.
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, 'report');
$saved = hash_final($context);
try { hash_final($context); }
catch (TypeError $error) { echo "Повторная финализация отклонена.\n"; }
echo "Ранее сохранённый отпечаток report: ", $saved, "\n";
Ожидаемый результат
Повторная финализация отклонена.
Ранее сохранённый отпечаток report: 845e91831319e89c4d656bdb80c278ac09a7230d61e5dfd2e1b1fbb436ac8917
Вывод показывает результат описанного выше опыта с указанными исходными данными.
Ошибки и ограничения. С 7.2 принимает объект. После final повторные hash_update/hash_final/hash_copy дают TypeError в текущем PHP, а прямой clone такого объекта — ValueError. Проверка instanceof HashContext не доказывает активность; состояние должно контролироваться вашим порядком вызовов.
Продолжить: hash_init, hash_copy, hash_update. Как выбрать задачу и алгоритм.
hash_copyОтветвить независимое вычисление после общего префикса.
hash_copy(HashContext $context): HashContextОтветвить два отчёта от общего заголовка. copy создаёт независимое состояние. Обычное $second = $first сохранило бы ссылку на тот же объект. Здесь заголовок один, а количество у двух продолжений разное.
- Исходный контекст содержит order=42 LF
- copy ответвляет независимое состояние
- Ветки получают qty=2 LF и qty=9 LF → разные отпечатки
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Ответвить независимое вычисление после общего префикса.
| Параметр / результат | Контракт |
|---|---|
| context | Активный контекст, который нужно скопировать. |
| return | Другой объект с тем же текущим состоянием и режимом; дальнейшие update/final независимы. |
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, "order=42\n");
$other = hash_copy($context);
hash_update($context, "qty=2\n");
hash_update($other, "qty=9\n");
echo 'Количество2: ', hash_final($context), "\n";
echo 'Количество9: ', hash_final($other), "\n";
Ожидаемый результат
Количество2: c061afdd8db7ecdf0b1ed01d735f3aebadc4d8f6e0d0cb42b6de4d41d3aad987
Количество9: 42ff9f4045168a6d0e5c6118703aa77232fface963bc464179e112d7e9894022
Каждый final закрывает свою ветвь. Для двух коротких строк обычный hash проще; выгода копирования возникает, если общий префикс действительно дорог в повторной обработке.
Обычное присваивание $second = $base указывало бы на тот же объект. Копия HMAC также содержит секретное рабочее состояние: не экспортируйте её и не считайте безопасным логом. Польза копирования зависит от длины общего префикса; для двух коротких строк отдельный hash_hmac проще.
<?php
declare(strict_types=1);
$key = 'demo-only-key';
$base = hash_init('sha256', HASH_HMAC, $key);
hash_update($base, 'shop:v1:');
$first = hash_copy($base);
$second = hash_copy($base);
hash_update($first, 'order:42');
hash_update($second, 'order:43');
var_dump(hash_final($first) === hash_hmac('sha256', 'shop:v1:order:42', $key));
var_dump(hash_final($second) === hash_hmac('sha256', 'shop:v1:order:43', $key));
Ожидаемый результат
bool(true)
bool(true)
Ошибки и ограничения. Доступно с 5.3, с 7.2 работает с HashContext. Невалидный/финализированный context даёт TypeError; невозможность внутреннего копирования — Error. Native clone активного HashContext тоже копирует состояние, но явный hash_copy легче узнаётся в потоковом коде.
Продолжить: hash_final, hash_init, HashContext::__serialize. Как выбрать задачу и алгоритм.
hash_update_streamДобавить байты из уже открытого потока, начиная с его текущей позиции.
hash_update_stream(HashContext $context, resource $stream, int $length = -1): intПрочитать ровно полезную часть пакета. packet.txt содержит HEADabcTAIL. fopen с rb открывает байты без преобразования строк, fseek4 пропускает четыре байта заголовка, length3 ограничивает чтение содержимым abc.
- packet.txt: HEADabcTAIL; fseek4 пропускает HEAD
- update_stream читает ровно3байта abc
- final выдаёт SHA256 abc; в потоке остался TAIL
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Добавить байты из уже открытого потока, начиная с его текущей позиции.
| Параметр / результат | Контракт |
|---|---|
| context | Активный HashContext. |
| stream | Открытый читаемый stream resource, например fopen. Он не закрывается и не перематывается этой функцией. |
| length | Максимум байтов, не Unicode-знаков. -1 означает читать до конца; 0 — не добавлять ничего. |
| return | Фактическое число добавленных байтов. Может быть меньше лимита при EOF/остановке чтения; 0 не доказывает успешность всего файла. |
<?php
declare(strict_types=1);
$stream = fopen(__DIR__ . '/packet.txt', 'rb');
if ($stream === false) { throw new RuntimeException('Пакет недоступен'); }
try {
if (fseek($stream, 4) !== 0) { throw new RuntimeException('Не удалось пропустить HEAD'); }
$context = hash_init('sha256');
if (hash_update_stream($context, $stream, 3) !== 3) { throw new RuntimeException('Пакет обрезан'); }
echo 'Поле abc: ', hash_final($context), "\n";
echo 'Остаток потока: ', stream_get_contents($stream), "\n";
} finally { fclose($stream); }
Ожидаемый результат
Поле abc: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Остаток потока: TAIL
Функция сдвинула курсор, но не закрыла поток. finally гарантирует наше закрытие при обычном исключении. Лимит измеряется байтами, не буквами; EOF до трёх байтов означает неполный пакет.
Контракт ожидал ровно пять байтов; три уже изменили context, поэтому ошибку нельзя исправить простым повтором всего файла в тот же контекст. Начинают новый расчёт после решения причины сбоя. Для чтения до EOF проверка зависит от известного размера и типа потока.
<?php
declare(strict_types=1);
$stream = fopen(__DIR__ . '/short.txt', 'rb');
if ($stream === false) { throw new RuntimeException('Open failed'); }
try {
$context = hash_init('sha256');
$added = hash_update_stream($context, $stream, 5);
echo $added, "\n";
if ($added !== 5) { echo 'incomplete: discard context'; }
else { echo hash_final($context); }
} finally { fclose($stream); }
Ожидаемый результат
3
incomplete: discard contextОшибки и ограничения. Неверный/закрытый stream resource и завершённый context приводят к TypeError. Ошибка чтения может дать warning и короткое число вместо исключения; возврат не равен гарантии целостности источника. С 7.2 context — объект. Для локальных бинарных файлов открывайте rb: текстовый режим может изменять байты на отдельных платформах.
Продолжить: hash_update_file, hash_file, hash_final. Как выбрать задачу и алгоритм.
hash_update_fileДобавить весь файл к уже накопленному префиксу вычисления.
hash_update_file(HashContext $context, string $filename, ?resource $stream_context = null): boolДобавить файл после версии формата. Первый аргумент — уже открытый HashContext, второй — имя файла. Это другой объект, чем stream_context с настройками чтения. Возвращаемый bool необходимо проверить до final.
- Контекст содержит format:v1 LF
- update_file добавляет весь export.txt с начала
- После успешного чтения final выдаёт digest версии и файла
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Добавить весь файл к уже накопленному префиксу вычисления.
| Параметр / результат | Контракт |
|---|---|
| context | Активный HashContext, уже содержащий ноль или больше байтов. |
| filename | Локальный путь либо разрешённый fopen wrapper. Файл открывается с начала в бинарном режиме и закрывается функцией. |
| stream_context | Опциональный контекст stream_context_create; это настройки чтения, а не HashContext. null поддерживается с 8.0. |
| return | true при полном успешном чтении; false при отказе открытия/чтения. Состояние могло получить часть данных до сбоя. |
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, "format:v1\n");
if (!hash_update_file($context, __DIR__ . '/export.txt')) { throw new RuntimeException('Отчёт не прочитан; результат не выдаём'); }
echo 'Версия и файл: ', hash_final($context), "\n";
Ожидаемый результат
Версия и файл: 075dcceb6e9f5aedd33f9fff27a41605f9e9021e53cf5db7b5835e3437d5c25c
Архив содержит export.txt с обеими строками. При сбое чтения часть файла уже могла попасть в контекст: нельзя выдать хеш только префикса как полный отчёт. Для нового повтора создают новый контекст.
Не финализируем только заголовок и не выдаём его за сумму полного документа. При частичном сбое нет автоматического rollback: новый повтор — новый контекст. Обработчик warning временный и нужен только для детерминированной демонстрации.
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, 'format-v1:');
$warnings = 0;
set_error_handler(function (int $level) use (&$warnings): bool {
if ($level !== E_WARNING) { return false; }
++$warnings; return true;
});
try { $ok = hash_update_file($context, __DIR__ . '/missing.txt'); }
finally { restore_error_handler(); }
if (!$ok) { unset($context); echo 'discarded', "\n"; }
var_dump($warnings > 0);
Ожидаемый результат
discarded
bool(true)
Ошибки и ограничения. Отказ I/O — warning/false; неверный context или stream_context — TypeError; NUL в filename — ValueError. Поддержка wrappers не разрешает принимать произвольный пользовательский URL. Если нужен один файл без префикса, hash_file короче и понятнее.
Продолжить: hash_file, hash_update_stream, hash_update, hash_final. Как выбрать задачу и алгоритм.
hash_hmac_algosПолучить список алгоритмов, для которых PHP реализует HMAC.
hash_hmac_algos(): arrayПроверить алгоритм перед проверкой экспорта. Список hash_hmac_algos содержит алгоритмы, допустимые именно для HMAC. Наличие имени не означает, что его разрешила политика сервиса. Выбираем sha256 явно и отказываем при отсутствии.
- Протокол требует HMAC-SHA256
- hash_hmac_algos проверяет доступность sha256
- MAC вычисляется по order=42 с учебным ключом
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Получить список алгоритмов, для которых PHP реализует HMAC.
| Параметр / результат | Контракт |
|---|---|
| Параметры | Нет. |
| return | Индексированный массив имён, допустимых для HMAC. В нём могут быть исторические алгоритмы; это не политика выбора. |
<?php
declare(strict_types=1);
if (!in_array('sha256', hash_hmac_algos(), true)) { throw new RuntimeException('Нет обязательного sha256'); }
echo "Проверка экспорта: sha256 доступен.\n";
echo 'Контрольный результат: ', hash_hmac('sha256', 'order=42', 'local-demo-key'), "\n";
Ожидаемый результат
Проверка экспорта: sha256 доступен.
Контрольный результат: 656a184e13c773994fcbc3e7a6030a261700ca9545352c0fb7142f52352e2e1b
Фиксированный ключ здесь только для известного тестового вектора; настоящие ключи хранят закрыто и генерируют случайно. Алгоритм из запроса нельзя принимать только потому, что он есть в этом списке.
Проверка происходит при настройке сервиса. Не переключаемся с неизвестного алгоритма на MD5 для «совместимости» и не берём алгоритм из незащищённого заголовка без протокольного правила.
<?php
declare(strict_types=1);
$configured = 'sha256';
$policy = ['sha256', 'sha512'];
if (!in_array($configured, $policy, true)
|| !in_array($configured, hash_hmac_algos(), true)) {
throw new RuntimeException('Unsupported configured MAC');
}
echo 'MAC configuration accepted';
Ожидаемый результат
MAC configuration acceptedОшибки и ограничения. Функция появилась в 7.2; раньше отдельного списка не было. Порядок и полный набор могут меняться между версиями. То, что MD5/SHA-1 исторически присутствует, не делает их разумным выбором нового протокола.
Продолжить: hash_algos, hash_hmac, hash_hkdf. Как выбрать задачу и алгоритм.
hash_hmac_fileВычислить MAC содержимого файла с известным обеим сторонам ключом.
hash_hmac_file(string $algo, string $filename, string $key, bool $binary = false): string|falseПринять подлинную учебную запись и отклонить изменение. HMAC вычисляет проверочную строку из точных байтов и отдельного общего ключа. Ключ — третий аргумент; дописать его к тексту и вызвать hash означало бы другую конструкцию. В опыте отправитель и получатель локальные, ключ намеренно учебный; вывод MAC здесь воспроизводим, реальный ключ не публикуют.
- export.txt содержит заказ42 и количество2
- hmac_file читает файл с учебным общим ключом
- Сравнение подтверждает оригинал и отклоняет другой текст
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Вычислить MAC содержимого файла с известным обеим сторонам ключом.
| Параметр / результат | Контракт |
|---|---|
| algo | Поддерживаемый криптографический алгоритм HMAC. |
| filename | Путь или fopen wrapper; в примерах контролируемый локальный файл. |
| key | Общий секретный ключ, не пароль пользователя и не имя файла. |
| binary | false — hex; true — двоичные байты для протокольного кодирования. |
| return | MAC string либо false при ошибке чтения. Имя файла и метаданные в MAC автоматически не включаются. |
<?php
declare(strict_types=1);
$known = hash_hmac_file('sha256', __DIR__ . '/export.txt', 'local-demo-key');
if ($known === false) { throw new RuntimeException('Файл не прочитан'); }
$received = $known;
echo 'Подпись: ', $known, "\n";
echo hash_equals($known, $received) ? "Исходная запись принята.\n" : "Подпись не совпала.\n";
$changed = hash_hmac('sha256', 'order=43', 'local-demo-key');
echo hash_equals($known, $changed) ? "Изменение принято.\n" : "Изменённая запись отклонена.\n";
Ожидаемый результат
Подпись: d9bcbf1b3f2fdb7cf18f3a4ca777e268b1a101282c62de32d548acd5d1c43444
Исходная запись принята.
Изменённая запись отклонена.
HMAC не скрывает текст и не предотвращает повтор того же сообщения. При протоколе с событиями добавляют аутентифицированные время/ID и атомарный учёт повторов. Для Base64 вызывайте hash_hmac с true, затем кодируйте сырые байты, а не hex.
Подписано количество 2, а fixture содержит 3. Реальный ожидаемый MAC принимают в определённом формате и проверяют его тип/длину; файл фиксируют от изменения до использования. HMAC не запрещает повторно импортировать тот же корректный файл.
<?php
declare(strict_types=1);
$key = 'demo-only-key';
$expected = hash_hmac('sha256', "id,qty\n42,2\n", $key, true);
$actual = hash_hmac_file('sha256', __DIR__ . '/changed.csv', $key, true);
if ($actual === false) { throw new RuntimeException('Read failed'); }
echo hash_equals($expected, $actual) ? 'import allowed' : 'reject before import';
Ожидаемый результат
reject before importОшибки и ограничения. Неизвестный/некриптографический algo — ValueError (современный контракт с 8.0); некриптографические HMAC запрещены с 7.2. Отказ I/O — warning/false; NUL в пути — ValueError. Не принимайте произвольные wrappers из запроса. Дополнительных algorithm options/stream_context в этой сигнатуре нет.
Продолжить: hash_hmac, hash_equals, hash_update_file, hash_init. Как выбрать задачу и алгоритм.
hash_hkdfПолучить отдельный ключ для конкретной роли из исходного ключевого материала.
hash_hkdf(string $algo, string $key, int $length = 0, string $info = "", string $salt = ""): stringРазделить ключи двух назначений. HKDF получает уже стойкий исходный ключ, длину результата в байтах, строку назначения info и salt. Это не медленная проверка человеческого пароля. Разные info отделяют подпись выгрузки от уведомления.
- Учебный master M×32 и demo-salt
- HKDF с info shop/export/v1 и shop/notify/v1 даёт разные ключи
- Подпись экспорта не принимается ключом уведомления
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Получить отдельный ключ для конкретной роли из исходного ключевого материала.
| Параметр / результат | Контракт |
|---|---|
| algo | Криптографический алгоритм из hash_hmac_algos. |
| key | Непустой исходный ключевой материал в байтах; для примера — фиксированный fixture, для сервиса качественный секрет. |
| length | Длина результата в байтах; 0 — длина digest выбранного алгоритма. Максимум 255×digest_size: для SHA-256 — 8160 байтов. |
| info | Публичный однозначный контекст назначения: например shop/export-signing/v1. Он отделяет роли и должен быть независим от значения key. |
| salt | Необязательная соль для стадии извлечения; пустая разрешена. Соль обычно не секретна, но её происхождение/хранение должны соответствовать протоколу. |
| return | Всегда двоичная строка производного ключа. Параметра binary и встроенного hex-режима нет. |
<?php
declare(strict_types=1);
$master = str_repeat('M', 32); // Only a reproducible local fixture.
$exportKey = hash_hkdf('sha256', $master, 32, 'shop/export/v1', 'demo-salt');
$notifyKey = hash_hkdf('sha256', $master, 32, 'shop/notify/v1', 'demo-salt');
$signature = hash_hmac('sha256', 'order=42', $exportKey);
echo hash_equals($signature, hash_hmac('sha256', 'order=42', $exportKey)) ? "Экспорт: подпись принята.\n" : "Экспорт: отказ.\n";
echo hash_equals($signature, hash_hmac('sha256', 'order=42', $notifyKey)) ? "Уведомление: подпись принята.\n" : "Уведомление: чужое назначение отклонено.\n";
Ожидаемый результат
Экспорт: подпись принята.
Уведомление: чужое назначение отклонено.
Повторяемое M×32 нужно только для теста. Реальный master берут из CSPRNG/системы ключей; одинаковые master/salt/info/length/algo восстанавливают одинаковые байты. Утечка master раскрывает производные ключи; info не является секретом.
Общее корневое значение не означает один ключ на все роли. Случайная соль от random_bytes должна сохраняться/передаваться по протоколу; генерация новой соли при каждой проверке изменила бы результат. Фиксированные разные info поддерживают разделение назначений; компрометация master компрометирует производные ключи.
<?php
declare(strict_types=1);
$master = str_repeat('M', 32);
$salt = 'demo-salt';
$exportKey = hash_hkdf('sha256', $master, 32, 'shop/export/v1', $salt);
$notifyKey = hash_hkdf('sha256', $master, 32, 'shop/notify/v1', $salt);
echo $exportKey !== $notifyKey ? "Ключи экспорта и уведомления разделены.\n" : "Назначения получили один ключ.\n";
$exportMac = hash_hmac('sha256', 'id=42', $exportKey);
$notifyMac = hash_hmac('sha256', 'id=42', $notifyKey);
echo hash_equals($exportMac, $notifyMac) ? "Подпись другого назначения принята.\n" : "Подпись другого назначения отклонена.\n";
Ожидаемый результат
Ключи экспорта и уведомления разделены.
Подпись другого назначения отклонена.
Вывод показывает результат описанного выше опыта с указанными исходными данными.
Размер ограничивается digest_size×255, а не длиной текстового hex. Ключ/вывод не печатаем в рабочей диагностике: достаточно категории отказа.
<?php
declare(strict_types=1);
foreach ([['', 32], ['demo', -1], ['demo', 8161]] as [$key, $length]) {
try { hash_hkdf('sha256', $key, $length); }
catch (ValueError $error) { echo 'rejected', "\n"; }
}
Ожидаемый результат
rejected
rejected
rejected
Ошибки и ограничения. Появилась в 7.1.2. С 8.0 ValueError при пустом key, неизвестном/некриптографическом algo, отрицательном или слишком большом length; раньше warning/false. HKDF концентрирует имеющуюся энтропию, не создаёт её из предсказуемого секрета. Не заменяет протокольный обмен ключами.
Продолжить: hash_pbkdf2, hash_hmac, hash_hmac_algos. Как выбрать задачу и алгоритм.
hash_pbkdf2Воспроизвести согласованное PBKDF2-выведение ключа из пароля.
hash_pbkdf2(string $algo, string $password, string $salt, int $iterations, int $length = 0, bool $binary = false, array $options = []): stringВосстановить ключ известного старого формата. Это низкоуровневое получение ключа из фразы: алгоритм, пароль, соль, итерации, длина, binary. При true длина означает байты; при false — hex-знаки. Для новых аккаунтов PHP предоставляет password_hash.
- Учебные password/salt,2итерации,16байтов
- PBKDF2-SHA256 формирует известный тестовый вектор
- Hex ae4d… подтверждает совместимость параметров
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Воспроизвести согласованное PBKDF2-выведение ключа из пароля.
| Параметр / результат | Контракт |
|---|---|
| algo | Криптографический HMAC-алгоритм, например sha256. Некриптографические варианты запрещены. |
| password | Байты пароля/секретной фразы. PHP не нормализует Unicode автоматически; формат должен быть согласован. |
| salt | Соль; для реального нового пароля/материала обычно генерируют random_bytes, сохраняют отдельно от секрета. Не секретна. |
| iterations | Положительное число внутренних раундов; влияет на стоимость вычисления. Значение задаёт доверенная политика/формат. |
| length | Длина ВЫХОДНОЙ строки: при binary=true байты, при false hex-знаки. 0 — полный размер digest, соответственно 32/64 для SHA-256. Нечётная hex-длина допустима, но требует точного внешнего контракта. |
| binary | false — нижний hex; true — двоичный производный ключ. |
| options | Дополнительные настройки алгоритма; для допустимого SHA-256 оставьте []. Murmur seed неприменим, потому что Murmur не разрешён PBKDF2. |
| return | Строка производного ключа; алгоритм/соль/iterations/формат не упакованы в неё автоматически. |
<?php
declare(strict_types=1);
$key = hash_pbkdf2('sha256', 'password', 'salt', 2, 16, true);
echo 'Тестовый ключ в hex: ', bin2hex($key), "\n";
$expected = hex2bin('ae4d0c95af6b46d32d0adff928f06dd0');
echo hash_equals($expected, $key) ? "Формат восстановлен; совместимость подтверждена.\n" : "Формат несовместим.\n";
Ожидаемый результат
Тестовый ключ в hex: ae4d0c95af6b46d32d0adff928f06dd0
Формат восстановлен; совместимость подтверждена.
Две итерации и опубликованная соль — исключительно короткий тестовый вектор, не стойкая настройка. Для реальных ключей параметры выбирают по стандарту протокола и бюджету; недоверенный файл не должен задавать неограниченное число итераций/байтов.
Метаданные здесь заданы программой; недоверенный файл нельзя бесконтрольно пускать в derivation. До вызова проверяют допустимый алгоритм, диапазон iterations/length, длину соли и общий бюджет работы. Иначе запрос может занять CPU/память. Не сохраняйте секретную фразу вместе с этими параметрами.
<?php
declare(strict_types=1);
$format = ['algo' => 'sha256', 'iterations' => 2,
'length' => 16, 'salt_hex' => bin2hex('fixture-salt')];
$salt = hex2bin($format['salt_hex']);
if ($salt === false) { throw new RuntimeException('Bad fixture salt'); }
$key = hash_pbkdf2($format['algo'], 'demo phrase', $salt,
$format['iterations'], $format['length'], true);
$again = hash_pbkdf2($format['algo'], 'demo phrase', $salt,
$format['iterations'], $format['length'], true);
echo "Метаданные формата: sha256,2раунда,16байтов, соль fixture-salt.\n";
echo hash_equals($key, $again) ? "По тем же метаданным восстановлен тот же ключ.\n" : "Ключи различаются.\n";
Ожидаемый результат
Метаданные формата: sha256,2раунда,16байтов, соль fixture-salt.
По тем же метаданным восстановлен тот же ключ.
Вывод показывает результат описанного выше опыта с указанными исходными данными.
Положительность — только минимальное условие PHP, не проверка достаточной стойкости. Стоимость растёт с iterations и количеством выходных блоков; измерение и ограничение частоты входа остаются задачей приложения.
<?php
declare(strict_types=1);
foreach ([[0, 16], [2, -1]] as [$rounds, $length]) {
try { hash_pbkdf2('sha256', 'demo', 'salt', $rounds, $length, true); }
catch (ValueError $error) { echo 'rejected', "\n"; }
}
Ожидаемый результат
rejected
rejected
Ошибки и ограничения. Функция с 5.5; запрет некриптографических алгоритмов с 7.2; с 8.0 ошибки дают ValueError: неподдерживаемый algo, iterations≤0, length<0, соль длиннее INT_MAX−4. Последнее — предел C int, не PHP_INT_MAX на 64-bit; огромную соль нельзя выделять ради теста. options добавлен в 8.3 (сверено по PHP stub 8.2/8.3); для базы PHP 8.2 достаточно первых шести аргументов. Неизвестные типы password/salt и других аргументов при strict_types=1 дают TypeError.
Продолжить: hash_hkdf, hash_hmac_algos, hash_equals. Как выбрать задачу и алгоритм.
HashContext::__constructПонять, почему контекст создают hash_init, а не через new.
private HashContext::__construct()Управлять состоянием вычисления заказа. Конструктор private: new HashContext недоступен. Рабочий объект создаёт фабрика hash_init, которая выбирает алгоритм и инициализирует состояние.
- Известные байты примера или приложенного файла
- Вызов HashContext::__construct получает эти байты/состояние
- Результат: Прямое создание закрыто; используем hash_init.
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Понять, почему контекст создают hash_init, а не через new.
| Параметр / результат | Контракт |
|---|---|
| Параметры | Нет. Конструктор закрыт для пользовательского кода. |
| return | Конструктор не возвращает значение; прямое создание недоступно. Рабочий объект возвращает hash_init. |
<?php
declare(strict_types=1);
try { new HashContext(); } catch (Error) { echo "Прямое создание закрыто; используем hash_init.\n"; }
$context = hash_init('sha256');
hash_update($context, 'order=42');
echo 'Отчёт: ', hash_final($context), "\n";
Ожидаемый результат
Прямое создание закрыто; используем hash_init.
Отчёт: e7be97a0dece7cd8751b91338ce03d916b01ee32616d2b8b70e1807e5f307705
Состояние может содержать исходные байты, поэтому не передавайте его посетителю и не помещайте в публичные журналы. Формат сериализации не обещает переносимость между версиями/архитектурами. Для обычного готового файла проще повторить hash_file; для ветвления в памяти — hash_copy.
Фабрика — обычная функция, которая возвращает подготовленный объект. Здесь два вызова дают два разных состояния. У HashContext нельзя сделать подкласс: класс final, его собственную реализацию расширяют композицией, если это действительно нужно.
<?php
declare(strict_types=1);
$first = hash_init('sha256');
$second = hash_init('sha256');
hash_update($first, 'first');
hash_update($second, 'second');
echo $first !== $second ? "Созданы независимые состояния двух документов.\n" : "Использовано одно состояние.\n";
echo "Документ first: ", hash_final($first), "\n";
echo "Документ second: ", hash_final($second), "\n";
Ожидаемый результат
Созданы независимые состояния двух документов.
Документ first: a7937b64b8caa58f03721bb6bacf5c78cb235febe0e70b1b84cd99541461a08e
Документ second: 16367aacb67a4a017c8da8ab95682ccb390863780f7114dda0a0e0c55644c7c4
Вывод показывает результат описанного выше опыта с указанными исходными данными.
Ошибки и ограничения. HashContext появился в PHP 7.2 вместо resource. Прямой вызов private-конструктора даёт Error. Нельзя обойти жизненный цикл ради пустого контейнера и затем работать с ним как с корректным хешем.
Продолжить: hash_init, hash_copy. Как выбрать задачу и алгоритм.
HashContext::__debugInfoУвидеть алгоритм контекста в диагностическом выводе.
HashContext::__debugInfo(): arrayУправлять состоянием вычисления заказа. В PHP8.4+ диагностический метод возвращает поле algo. Стрелка вызывает метод именно этого объекта. Диагностика не завершает вычисление и не даёт хеш исходных данных.
- Известные байты примера или приложенного файла
- Вызов HashContext::__debugInfo получает эти байты/состояние
- Результат: Выбран алгоритм: sha256
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Увидеть алгоритм контекста в диагностическом выводе.
| Параметр / результат | Контракт |
|---|---|
| Параметры | Нет. var_dump вызывает hook при выводе объекта; метод не предназначен для управления вычислением. |
| return | Ассоциативный массив диагностики с ключом algo и именем алгоритма. Это не digest, не ключ и не все внутренние поля. |
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, 'order=');
$info = $context->__debugInfo();
echo 'Выбран алгоритм: ', $info['algo'], "\n";
hash_update($context, '42');
echo 'Отчёт: ', hash_final($context), "\n";
Ожидаемый результат
Выбран алгоритм: sha256
Отчёт: e7be97a0dece7cd8751b91338ce03d916b01ee32616d2b8b70e1807e5f307705
Состояние может содержать исходные байты, поэтому не передавайте его посетителю и не помещайте в публичные журналы. Формат сериализации не обещает переносимость между версиями/архитектурами. Для обычного готового файла проще повторить hash_file; для ветвления в памяти — hash_copy.
Прямой вызов сделан только для изучения возвращаемого массива hook. В прикладном коде алгоритм хранится в собственной конфигурации; не строят протокол на debug-выводе. Стрелка -> вызывает метод конкретного объекта; результат — обычный массив.
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, 'a');
$info = $context->__debugInfo();
echo $info['algo'], "\n";
hash_update($context, 'bc');
echo "После диагностики продолжен abc: ", hash_final($context), "\n";
Ожидаемый результат
sha256
После диагностики продолжен abc: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Вывод показывает результат описанного выше опыта с указанными исходными данными.
Ошибки и ограничения. Метод добавлен в 8.4: на 8.2/8.3 этого API нет. Не использовать debug-массив для проверки, завершён ли context: он не обещает такого поля. Не выводить ключи/сырые сообщения вручную рядом с безопасным полем algo.
Продолжить: hash_init, hash_final. Как выбрать задачу и алгоритм.
HashContext::__serializeСохранить поддерживаемое активное состояние обычного хеширования.
HashContext::__serialize(): arrayУправлять состоянием вычисления заказа. serialize вызывает __serialize и получает поддерживаемое состояние незавершённого расчёта. Снимок в примере создаётся самим PHP из собственных байтов в том же процессе. HMAC-состояние экспортировать нельзя.
- Известные байты примера или приложенного файла
- Вызов HashContext::__serialize получает эти байты/состояние
- Результат: Продолженный отчёт: e7be97a0dece7cd8751b91338ce03d916b01ee32616d2b8b70e1807e5f307705
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Сохранить поддерживаемое активное состояние обычного хеширования.
| Параметр / результат | Контракт |
|---|---|
| Параметры | Нет. Обычно PHP вызывает метод из serialize($context). |
| return | Массив внутреннего состояния для механизма сериализации. Это не строка digest; serialize вокруг него формирует отдельную строку представления объекта. |
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, 'order=');
$ownSnapshot = serialize($context);
$restored = unserialize($ownSnapshot, ['allowed_classes' => [HashContext::class]]);
if (!$restored instanceof HashContext) { throw new RuntimeException('Не восстановлен собственный снимок'); }
hash_update($restored, '42');
echo 'Продолженный отчёт: ', hash_final($restored), "\n";
hash_update($context, '43');
echo 'Независимый оригинал: ', hash_final($context), "\n";
Ожидаемый результат
Продолженный отчёт: e7be97a0dece7cd8751b91338ce03d916b01ee32616d2b8b70e1807e5f307705
Независимый оригинал: 96df9bbc8108b8389a59245f6814791cb08630fe0d63cacd70fe5127ee635ea0
Состояние может содержать исходные байты, поэтому не передавайте его посетителю и не помещайте в публичные журналы. Формат сериализации не обещает переносимость между версиями/архитектурами. Для обычного готового файла проще повторить hash_file; для ветвления в памяти — hash_copy.
HMAC запрещён, чтобы не экспортировать секретное состояние. Финализация уже освободила рабочий буфер. Вместо сохранения результата вычисления храните сам возвращённый digest; для разветвления активного процесса используйте hash_copy.
<?php
declare(strict_types=1);
$hmac = hash_init('sha256', HASH_HMAC, 'demo-only-key');
try { serialize($hmac); }
catch (Exception $error) { echo 'HMAC not serializable', "\n"; }
$finished = hash_init('sha256');
hash_final($finished);
try { serialize($finished); }
catch (Exception $error) { echo 'finished state rejected'; }
Ожидаемый результат
HMAC not serializable
finished state rejectedОшибки и ограничения. Метод существует с 8.0. В PHP 8.5 сериализация HMAC, завершённого состояния SHA-256 или неподдерживаемого алгоритмом формата вызывает Exception. Внутренний массив включает алгоритм, режим, состояние, версию представления и свойства; схему не следует собирать вручную. Состояние может содержать буфер последних байтов исходного сообщения: не логируйте его и не передавайте клиенту.
Продолжить: HashContext::__unserialize, hash_copy, hash_final. Как выбрать задачу и алгоритм.
HashContext::__unserializeВосстановить собственный доверенный снимок HashContext для продолжения вычисления.
HashContext::__unserialize(array $data): voidУправлять состоянием вычисления заказа. unserialize создаёт восстановленный объект и вызывает его __unserialize. allowed_classes ограничивает тип, но не делает чужие данные безопасными. Снимок в примере создаётся самим PHP из собственных байтов в том же процессе. HMAC-состояние экспортировать нельзя.
- Известные байты примера или приложенного файла
- Вызов HashContext::__unserialize получает эти байты/состояние
- Результат: Продолженный отчёт: e7be97a0dece7cd8751b91338ce03d916b01ee32616d2b8b70e1807e5f307705
Распакуйте все файлы архива вместе. Команда php example.php выполняет этот пример; файлы из архива не нужно придумывать самостоятельно.
Восстановить собственный доверенный снимок HashContext для продолжения вычисления.
| Параметр / результат | Контракт |
|---|---|
| data | Внутренний массив, полученный штатным __serialize. Обычно его передаёт unserialize, а не код приложения. |
| return | void: после успешного восстановления изменяется состояние объекта; отдельного digest не возвращается. |
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, 'order=');
$ownSnapshot = serialize($context);
$restored = unserialize($ownSnapshot, ['allowed_classes' => [HashContext::class]]);
if (!$restored instanceof HashContext) { throw new RuntimeException('Не восстановлен собственный снимок'); }
hash_update($restored, '42');
echo 'Продолженный отчёт: ', hash_final($restored), "\n";
hash_update($context, '43');
echo 'Независимый оригинал: ', hash_final($context), "\n";
Ожидаемый результат
Продолженный отчёт: e7be97a0dece7cd8751b91338ce03d916b01ee32616d2b8b70e1807e5f307705
Независимый оригинал: 96df9bbc8108b8389a59245f6814791cb08630fe0d63cacd70fe5127ee635ea0
Состояние может содержать исходные байты, поэтому не передавайте его посетителю и не помещайте в публичные журналы. Формат сериализации не обещает переносимость между версиями/архитектурами. Для обычного готового файла проще повторить hash_file; для ветвления в памяти — hash_copy.
Учебный прямой вызов демонстрирует запрет: active object нельзя переинициализировать таким способом. Нормальный маршрут — unserialize доверенного снимка в другой объект. Не строим ручной обход private-конструктора через Reflection.
<?php
declare(strict_types=1);
$context = hash_init('sha256');
hash_update($context, 'ab');
$state = $context->__serialize();
try { $context->__unserialize($state); }
catch (Exception $error) { echo 'already initialized', "\n"; }
hash_update($context, 'c');
echo "Исходный контекст продолжил abc: ", hash_final($context), "\n";
Ожидаемый результат
already initialized
Исходный контекст продолжил abc: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Вывод показывает результат описанного выше опыта с указанными исходными данными.
Ошибки и ограничения. С 8.0. Неверный тип data даёт TypeError; уже активный объект, повреждённая/неполная схема, неизвестный алгоритм, HMAC-режим или непригодное внутреннее состояние — Exception в текущей реализации. Не подавляйте эти ошибки и не принимайте snapshots извне. Формат не гарантированно переносим между PHP/архитектурами.
Продолжить: HashContext::__serialize, hash_init, hash_copy. Как выбрать задачу и алгоритм.
Источники и обсуждения
- PHP: book.hash
- PHP: hash.setup
- PHP: hash.installation
- PHP: hash.resources
- PHP: hash.constants
- PHP: class.hashcontext
- PHP: ref.hash
- PHP: function.hash
- PHP: function.hash-algos
- PHP: function.hash-copy
- PHP: function.hash-equals
- PHP: function.hash-file
- PHP: function.hash-final
- PHP: function.hash-hkdf
- PHP: function.hash-hmac
- PHP: function.hash-hmac-algos
- PHP: function.hash-hmac-file
- PHP: function.hash-init
- PHP: function.hash-pbkdf2
- PHP: function.hash-update
- PHP: function.hash-update-file
- PHP: function.hash-update-stream
- PHP: hashcontext.construct
- PHP: hashcontext.debuginfo
- PHP: hashcontext.serialize
- PHP: hashcontext.unserialize
- PHP 8.5.11 hash implementation
- PHP 8.5.11 xxHash options
- RFC 5869: HKDF
- OWASP: Password Storage
- Обсуждение задачи: Подпись JSON не совпадает между PHP и Node — обсуждение проблемы
- Обсуждение задачи: Base64 от hex вместо Base64 от двоичного HMAC — обсуждение проблемы
- Обсуждение задачи: Большой файл и потребление памяти — обсуждение проблемы
- Обсуждение задачи: Порядок аргументов hash_equals — обсуждение проблемы
- Обсуждение задачи: Совместимость производного ключа PHP и Java — обсуждение проблемы
- Обсуждение задачи: Подпись webhook и пустой HTTP-ответ — обсуждение проблемы