Skip to content

Всё для WordPress, веб-разработки — и не только

🔒 Шифрование и дешифрование файлов в PHP: OpenSSL и Sodium вместо mcrypt

🔒 Шифрование и дешифрование файлов в PHP: OpenSSL и Sodium вместо mcrypt

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

Расширение mcrypt помечено deprecated в PHP 7.1 и полностью удалено в PHP 7.2 ещё в 2017 году. Сегодня, на PHP 8, попытка вызвать mcrypt_encrypt() заканчивается fatal error. Между тем на серверах по-прежнему лежат конфиденциальные файлы: бэкапы баз данных, CSV с персональными данными, PDF-договоры, экспорты заказов.

Хорошая новость: PHP из коробки даёт два работающих механизма, OpenSSL и Sodium. Оба не требуют установки дополнительных расширений на современном хостинге, оба быстрее и безопаснее mcrypt. Ниже, практическое руководство по шифрованию и расшифровке файлов на PHP 8 с кодом, который можно скопировать и запустить.

💡 Быстрый обзор:

  • Почему mcrypt мёртв и какие версии PHP затронуты
  • Пошаговое шифрование файла через OpenSSL с AES-256-CBC
  • Расшифровка с защитой от подмены данных через HMAC
  • Альтернатива на Sodium для PHP 7.2 и выше
  • Когда выбирать OpenSSL, а когда Sodium

Почему mcrypt больше не вариант

Библиотека mcrypt не обновлялась с 2007 года. В её коде нашли критические уязвимости, а мейнтейнеров не осталось. Команда PHP приняла решение: в PHP 7.1 расширение помечено как устаревшее, а в PHP 7.2, вышедшем в ноябре 2017, полностью исключено из ядра.

Если вы переносите старый проект с mcrypt, загляните в phpinfo(). На PHP 7.2+ строчки «mcrypt support: enabled» там нет. Вызов mcrypt_encrypt(), mcrypt_decrypt() или stream-фильтров mcrypt.tripledes / mdecrypt.tripledes возвращает ошибку «Call to undefined function».

Формально mcrypt доступен через PECL, командой pecl install mcrypt. Но ставить неподдерживаемое расширение с известными уязвимостями на боевой сервер ради одного легаси-скрипта, плохая идея. Перепишите шифрование на OpenSSL: он встроен в PHP с версии 5.3 и никуда не денется.

Шифрование файла через OpenSSL: пошагово

OpenSSL в PHP представлен функциями openssl_encrypt() и openssl_decrypt(). Они работают с сырыми данными и поддерживают десятки алгоритмов, от AES-128-CBC до AES-256-GCM. Для файлов мы используем AES-256-CBC: он криптостойкий и не требует PHP 7.1 в отличие от GCM с дополнительными параметрами тегов.

Шаг 1: генерируем ключ шифрования

Ключ, главный секрет всей схемы. Он должен быть криптографически случайным, а не придуманным вручную. Никаких «secret-password» из примера, только openssl_random_pseudo_bytes().

Скрипт ниже генерирует 256-битный ключ и выводит его в формате для вставки в wp-config.php. Запустите один раз через командную строку и сохраните результат:

1<?php
2// Генерация случайного 256-битного ключа (32 байта)
3$encryption_key = base64_encode(openssl_random_pseudo_bytes(32));
4echo "define('FILE_ENCRYPTION_KEY', '" . $encryption_key . "');\n";

Функция openssl_random_pseudo_bytes(32) возвращает 32 байта криптографически качественной случайности. base64_encode приводит бинарные данные к строке, которую удобно хранить в конфигурационных файлах. Ключ должен быть вне document root, в wp-config.php или .env, но не в коде темы.

Шаг 2: функция шифрования файла

Скрипт читает файл с диска, шифрует алгоритмом AES-256-CBC, добавляет в начало случайный IV и HMAC-подпись для проверки целостности, сохраняет результат. Добавьте код в functions.php дочерней темы или в кастомный плагин.

Внимание: перед запуском на боевом сервере сделайте полный бэкап. Протестируйте шифрование-расшифровку на копии файла в тестовой директории. Если ключ утерян, расшифровать данные невозможно, AES-256 не поддаётся брутфорсу.

1<?php
2function encrypt_file(string $sourcePath, string $destPath, string $key): bool
3{
4 if (!file_exists($sourcePath)) {
5 throw new RuntimeException('Исходный файл не найден: ' . $sourcePath);
6 }
7
8 $plaintext = file_get_contents($sourcePath);
9 if ($plaintext === false) {
10 throw new RuntimeException('Не удалось прочитать файл');
11 }
12
13 $cipher = 'aes-256-cbc';
14 $ivLength = openssl_cipher_iv_length($cipher);
15 $iv = openssl_random_pseudo_bytes($ivLength);
16
17 $ciphertext = openssl_encrypt(
18 $plaintext,
19 $cipher,
20 base64_decode($key),
21 OPENSSL_RAW_DATA,
22 $iv
23 );
24
25 if ($ciphertext === false) {
26 throw new RuntimeException('Ошибка шифрования');
27 }
28
29 // HMAC-подпись для проверки целостности при расшифровке
30 $hmac = hash_hmac('sha256', $iv . $ciphertext, base64_decode($key), true);
31
32 // Формат файла: IV (16 байт) + HMAC (32 байта) + шифротекст
33 $result = file_put_contents($destPath, $iv . $hmac . $ciphertext);
34
35 return $result !== false;
36}

Что здесь происходит построчно:

  • openssl_cipher_iv_length('aes-256-cbc') возвращает 16, длину вектора инициализации для этого алгоритма.
  • openssl_random_pseudo_bytes($ivLength) создаёт случайный IV. Он делает так, что одинаковые данные, зашифрованные одним ключом, дают разный шифротекст при каждом запуске.
  • OPENSSL_RAW_DATA указывает функции вернуть бинарные данные, а не base64. Мы сохраняем сырой шифротекст для компактности.
  • hash_hmac('sha256', ...) вычисляет контрольную сумму от связки IV-и-шифротекст. При расшифровке пересчитаем HMAC и сравним: если данные были изменены или повреждены, сравнение не сойдётся.
  • Файл сохраняется в формате: [IV 16 байт][HMAC 32 байта][шифротекст]. Никаких разделителей, позиции фиксированы длинами.

Шаг 3: функция расшифровки

Обратный процесс: читаем IV, читаем HMAC, читаем шифротекст, пересчитываем HMAC и сравниваем через hash_equals(), расшифровываем. Код добавляется в тот же файл:

1<?php
2function decrypt_file(string $sourcePath, string $key): string|false
3{
4 if (!file_exists($sourcePath)) {
5 throw new RuntimeException('Зашифрованный файл не найден: ' . $sourcePath);
6 }
7
8 $data = file_get_contents($sourcePath);
9 if ($data === false) {
10 throw new RuntimeException('Не удалось прочитать файл');
11 }
12
13 $cipher = 'aes-256-cbc';
14 $ivLength = openssl_cipher_iv_length($cipher);
15 $hmacLength = 32; // sha256 = 32 байта
16
17 $iv = substr($data, 0, $ivLength);
18 $hmac = substr($data, $ivLength, $hmacLength);
19 $ciphertext = substr($data, $ivLength + $hmacLength);
20
21 // Проверка целостности: пересчитываем HMAC и сравниваем
22 $calculatedHmac = hash_hmac(
23 'sha256',
24 $iv . $ciphertext,
25 base64_decode($key),
26 true
27 );
28
29 if (!hash_equals($hmac, $calculatedHmac)) {
30 throw new RuntimeException('Файл повреждён или ключ неверен');
31 }
32
33 $plaintext = openssl_decrypt(
34 $ciphertext,
35 $cipher,
36 base64_decode($key),
37 OPENSSL_RAW_DATA,
38 $iv
39 );
40
41 return $plaintext;
42}

Ключевой момент: hash_equals() вместо ===. Обычное сравнение строк подвержено timing-атакам, злоумышленник может подбирать HMAC побайтово, замеряя время ответа сервера. hash_equals() сравнивает строки за константное время независимо от того, на каком символе они разошлись.

Пример использования с реальными путями

1<?php
2$key = FILE_ENCRYPTION_KEY; // из wp-config.php
3
4// Шифруем бэкап базы данных
5encrypt_file(
6 __DIR__ . '/backup.sql',
7 __DIR__ . '/backup.sql.enc',
8 $key
9);
10
11// Расшифровываем и отдаём на скачивание
12$decrypted = decrypt_file(__DIR__ . '/backup.sql.enc', $key);
13header('Content-Type: application/octet-stream');
14header('Content-Disposition: attachment; filename="backup.sql"');
15echo $decrypted;

Функции универсальны: работают с любым типом файлов, изображения, PDF, CSV, SQL-дампы. Размер ограничен только доступной оперативной памятью, поскольку файл читается в память целиком. Для гигабайтных файлов потребуется потоковая обработка с буферизацией чанков, но для подавляющего большинства практических задач этого кода достаточно.

Альтернатива: Sodium (libsodium)

Расширение Sodium встроено в PHP начиная с версии 7.2 и стало частью ядра в PHP 8.1. Оно предоставляет более простой API по сравнению с OpenSSL: не нужно вручную управлять IV и HMAC, аутентифицированное шифрование работает из коробки.

1<?php
2function sodium_encrypt_file(string $sourcePath, string $destPath, string $key): bool
3{
4 $plaintext = file_get_contents($sourcePath);
5 $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
6 $ciphertext = sodium_crypto_secretbox($plaintext, $nonce, base64_decode($key));
7 return file_put_contents($destPath, $nonce . $ciphertext) !== false;
8}
9
10function sodium_decrypt_file(string $sourcePath, string $key): string|false
11{
12 $data = file_get_contents($sourcePath);
13 $nonce = substr($data, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
14 $ciphertext = substr($data, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
15 $result = sodium_crypto_secretbox_open($ciphertext, $nonce, base64_decode($key));
16 return $result !== false ? $result : false;
17}

Обратите внимание: Sodium сам проверяет целостность. Если данные были изменены или ключ неверен, sodium_crypto_secretbox_open() просто возвращает false, никаких отдельных HMAC-проверок не требуется. Код получается вдвое короче.

Недостаток один: Sodium требует PHP 7.2 или выше. Если проект крутится на PHP 7.0-7.1, единственный вариант, OpenSSL. Но на практике в 2026 году найти хостинг с PHP ниже 7.4 уже сложно: по данным WordPress.org на июнь 2026, доля PHP 7.0-7.1 составляет менее 0,3% от всех установок.

OpenSSL или Sodium: два критерия выбора

Выбор между двумя подходами сводится к двум факторам:

  • Версия PHP. PHP 7.2+, берите Sodium, он безопаснее по умолчанию и не даёт ошибиться с реализацией HMAC. PHP 7.0-7.1, только OpenSSL. Ниже PHP 7.0, пора обновлять сервер, а не изобретать костыли с PECL-mcrypt.
  • Переносимость данных между окружениями. OpenSSL доступен везде, включая PHP 5.3+. Если зашифрованные файлы должны читаться и на dev-сервере, и на проде, и у заказчика, OpenSSL надёжнее с точки зрения совместимости. Sodium-шифротекст расшифруется только там, где есть Sodium.

На практике мы используем OpenSSL на проектах, где важна совместимость между разными окружениями. Sodium, там, где весь стек обновлён до PHP 8 и безопасность на первом месте.

В видео Dave Hollingworth детально разбирает оба подхода с демонстрацией кода и объяснением криптографических примитивов за каждым из них. Материал дополняет статью: показаны edge-кейсы, установка библиотеки defuse/php-encryption через Composer и сравнение производительности OpenSSL и Sodium на реальных данных.

⁉️🤔 Частые вопросы

Можно ли использовать md5() или sha1() для шифрования файлов?

Нет. md5() и sha1(), хеш-функции, они необратимы по определению. Зашифрованный файл должен расшифровываться обратно, а хеш расшифровать нельзя. Хеши применяются для проверки целостности (как HMAC в коде выше) и хранения паролей через password_hash(), но не для шифрования содержимого.

Что делать, если ключ шифрования утерян?

Расшифровать данные без ключа невозможно. Храните ключ в wp-config.php вне document root и делайте его бэкап отдельно от бэкапов файлов и базы данных. Не кладите ключ в репозиторий Git, добавьте wp-config.php в .gitignore или используйте переменные окружения.

Зачем нужен IV, если ключ и так секретный?

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

Можно ли шифровать большие файлы, несколько гигабайт?

Функции выше читают файл в память целиком, для гигабайтных данных это приведёт к исчерпанию памяти. Для потокового шифрования используйте openssl_encrypt() в цикле с буферизацией по чанкам (например, по 1 МБ) или библиотеку defuse/php-encryption, которая поддерживает потоковый режим из коробки.

Работает ли этот код на PHP 8.3?

Да. И OpenSSL, и Sodium полностью поддерживаются в PHP 8. Код протестирован на актуальных версиях PHP и не использует deprecated-функций. На PHP 8.3 совместимость сохраняется, обратных несовместимостей в OpenSSL-расширении нет.

Шифрование файлов в 2026 году: практический вердикт

Mcrypt ушёл из PHP, и это к лучшему. Два встроенных инструмента на замену и безопаснее, и быстрее, и не требуют PECL-танцев с бубном. OpenSSL работает везде, Sodium проще и надёжнее по умолчанию.

Если коротко: новый проект на PHP 8, стартуйте с Sodium, код получается чище. Переносите старый код с mcrypt, перепишите на OpenSSL, он доступен даже на PHP 7.0. И главное правило криптографии: храните ключи отдельно от зашифрованных данных. Потеря ключа = потеря данных, и брутфорс тут не спасёт.

Начните с тестового скрипта на копии файла: убедитесь, что цикл «зашифровал → расшифровал → побайтово совпало» отрабатывает без ошибок. А какой способ используете вы на своих проектах, OpenSSL, Sodium или что-то ещё? Напишите в комментариях.