
🔒 Шифрування та дешифрування файлів у 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)); 4 echo "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 2 function 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 2 function 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 // Шифруем бэкап базы данных 5 encrypt_file( 6 __DIR__ . '/backup.sql', 7 __DIR__ . '/backup.sql.enc', 8 $key 9 ); 10 11 // Расшифровываем и отдаём на скачивание 12 $decrypted = decrypt_file(__DIR__ . '/backup.sql.enc', $key); 13 header('Content-Type: application/octet-stream'); 14 header('Content-Disposition: attachment; filename="backup.sql"'); 15 echo $decrypted;
Функції універсальні: працюють із будь-яким типом файлів, зображення, PDF, CSV, SQL-дампи. Розмір обмежений лише доступною оперативною пам'яттю, оскільки файл читається в пам'ять цілком. Для гігабайтних файлів знадобиться потокова обробка з буферизацією чанків, але для переважної більшості практичних задач цього коду достатньо.
Альтернатива: Sodium (libsodium)
Розширення Sodium вбудоване в PHP починаючи з версії 7.2 і стало частиною ядра в PHP 8.1. Воно надає простіший API порівняно з OpenSSL: не потрібно вручну керувати IV і HMAC, автентифіковане шифрування працює з коробки.
1 <?php 2 function 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 10 function 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 чи щось інше? Напишіть у коментарях.



