
🔒 Szyfrowanie i deszyfrowanie plików w PHP: OpenSSL i Sodium zamiast mcrypt
Zaszyfrować plik przed zapisaniem na serwer i mieć pewność, że bez klucza nie odczyta go nikt, nawet administrator hostingu. Brzmi jak podstawowa potrzeba, ale wraz z usunięciem mcrypt z PHP dotychczasowy sposób przestał działać.
Rozszerzenie mcrypt zostało oznaczone jako deprecated w PHP 7.1 i całkowicie usunięte w PHP 7.2 jeszcze w 2017 roku. Dziś, na PHP 8, próba wywołania mcrypt_encrypt() kończy się błędem krytycznym. Tymczasem na serwerach wciąż leżą poufne pliki: kopie zapasowe baz danych, CSV z danymi osobowymi, umowy PDF, eksporty zamówień.
Dobra wiadomość: PHP od razu daje dwa działające mechanizmy, OpenSSL i Sodium. Oba nie wymagają instalowania dodatkowych rozszerzeń na nowoczesnym hostingu, oba są szybsze i bezpieczniejsze od mcrypt. Poniżej praktyczny poradnik szyfrowania i deszyfrowania plików w PHP 8 z kodem, który można skopiować i uruchomić.
💡 Szybki przegląd:
- Dlaczego mcrypt jest martwy i których wersji PHP to dotyczy
- Szyfrowanie pliku krok po kroku przez OpenSSL z AES-256-CBC
- Deszyfrowanie z ochroną przed podmianą danych przez HMAC
- Alternatywa na Sodium dla PHP 7.2 i wyższych
- Kiedy wybrać OpenSSL, a kiedy Sodium
Dlaczego mcrypt nie wchodzi już w grę
Biblioteka mcrypt nie była aktualizowana od 2007 roku. W jej kodzie znaleziono krytyczne luki, a maintainerów zabrakło. Zespół PHP podjął decyzję: w PHP 7.1 rozszerzenie oznaczono jako przestarzałe, a w PHP 7.2, wydanym w listopadzie 2017, całkowicie usunięto je z jądra.
Jeśli przenosi Pan stary projekt z mcrypt, proszę zajrzeć do phpinfo(). Na PHP 7.2+ nie ma tam wiersza „mcrypt support: enabled". Wywołanie mcrypt_encrypt(), mcrypt_decrypt() lub filtrów strumieni mcrypt.tripledes / mdecrypt.tripledes zwraca błąd „Call to undefined function".
Formalnie mcrypt jest dostępny przez PECL, komendą pecl install mcrypt. Jednak instalowanie niewspieranego rozszerzenia ze znanymi lukami na serwerze produkcyjnym dla jednego legacy skryptu to zły pomysł. Proszę przepisać szyfrowanie na OpenSSL: jest on wbudowany w PHP od wersji 5.3 i nigdzie się nie wybiera.
Szyfrowanie pliku przez OpenSSL: krok po kroku
OpenSSL w PHP reprezentują funkcje openssl_encrypt() i openssl_decrypt(). Pracują one na surowych danych i obsługują dziesiątki algorytmów, od AES-128-CBC po AES-256-GCM. Dla plików używamy AES-256-CBC: jest kryptograficznie odporny i nie wymaga PHP 7.1, w przeciwieństwie do GCM z dodatkowymi parametrami tagów.
Krok 1: generujemy klucz szyfrowania
Klucz to główny sekret całego schematu. Musi być kryptograficznie losowy, a nie wymyślony ręcznie. Żadnych „secret-password" z przykładu, tylko openssl_random_pseudo_bytes().
Poniższy skrypt generuje 256-bitowy klucz i wypisuje go w formacie do wklejenia do wp-config.php. Proszę uruchomić raz z linii poleceń i zapisać wynik:
1 <?php 2 // Генерация случайного 256-битного ключа (32 байта) 3 $encryption_key = base64_encode(openssl_random_pseudo_bytes(32)); 4 echo "define('FILE_ENCRYPTION_KEY', '" . $encryption_key . "');\n";
Funkcja openssl_random_pseudo_bytes(32) zwraca 32 bajty kryptograficznie wysokiej jakości losowości. base64_encode zamienia dane binarne na ciąg znaków, który wygodnie przechowywać w plikach konfiguracyjnych. Klucz musi być poza document root, w wp-config.php lub .env, ale nie w kodzie szablonu.
Krok 2: funkcja szyfrowania pliku
Skrypt odczytuje plik z dysku, szyfruje algorytmem AES-256-CBC, dodaje na początek losowy IV i podpis HMAC do weryfikacji integralności, zapisuje wynik. Proszę dodać kod do functions.php szablonu potomnego lub do niestandardowej wtyczki.
Uwaga: przed uruchomieniem na serwerze produkcyjnym proszę wykonać pełną kopię zapasową. Proszę przetestować szyfrowanie-deszyfrowanie na kopii pliku w katalogu testowym. Jeśli klucz zostanie utracony, odszyfrowanie danych jest niemożliwe, AES-256 nie poddaje się brute force.
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 }
Co tu się dzieje, linijka po linijce:
openssl_cipher_iv_length('aes-256-cbc')zwraca 16, długość wektora inicjalizującego dla tego algorytmu.openssl_random_pseudo_bytes($ivLength)tworzy losowy IV. Sprawia on, że te same dane zaszyfrowane tym samym kluczem dają inny szyfrogram przy każdym uruchomieniu.OPENSSL_RAW_DATAkaże funkcji zwrócić dane binarne, a nie base64. Przechowujemy surowy szyfrogram dla zwartości.hash_hmac('sha256', ...)oblicza sumę kontrolną z pary IV-i-szyfrogram. Przy deszyfrowaniu przeliczymy HMAC i porównamy: jeśli dane zostały zmienione lub uszkodzone, porównanie się nie powiedzie.- Plik zapisywany jest w formacie:
[IV 16 байт][HMAC 32 байта][шифротекст]. Żadnych separatorów, pozycje są stałe ze względu na długości.
Krok 3: funkcja deszyfrowania
Proces odwrotny: odczytujemy IV, odczytujemy HMAC, odczytujemy szyfrogram, przeliczamy HMAC i porównujemy przez hash_equals(), deszyfrujemy. Kod dodaje się do tego samego pliku:
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 }
Kluczowy moment: hash_equals() zamiast ===. Zwykłe porównanie ciągów jest podatne na ataki czasowe, napastnik może dobierać HMAC bajt po bajcie, mierząc czas odpowiedzi serwera. hash_equals() porównuje ciągi w stałym czasie, niezależnie od tego, na którym znaku się różnią.
Przykład użycia z rzeczywistymi ścieżkami
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;
Funkcje są uniwersalne: działają z każdym typem plików, obrazami, PDF, CSV, zrzutami SQL. Rozmiar ogranicza jedynie dostępna pamięć RAM, ponieważ plik wczytywany jest do pamięci w całości. Dla gigabajtowych plików potrzebne będzie przetwarzanie strumieniowe z buforowaniem fragmentów, ale dla zdecydowanej większości praktycznych zadań ten kod jest wystarczający.
Alternatywa: Sodium (libsodium)
Rozszerzenie Sodium jest wbudowane w PHP od wersji 7.2 i stało się częścią jądra w PHP 8.1. Oferuje ono prostsze API w porównaniu z OpenSSL: nie trzeba ręcznie zarządzać IV i HMAC, uwierzytelnione szyfrowanie działa od razu.
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 }
Proszę zwrócić uwagę: Sodium sam sprawdza integralność. Jeśli dane zostały zmienione lub klucz jest nieprawidłowy, sodium_crypto_secretbox_open() po prostu zwraca false, nie potrzeba żadnych osobnych kontroli HMAC. Kod wychodzi dwukrotnie krótszy.
Jest jedna wada: Sodium wymaga PHP 7.2 lub wyższego. Jeśli projekt działa na PHP 7.0-7.1, jedyną opcją jest OpenSSL. Ale w praktyce w 2026 roku znalezienie hostingu z PHP poniżej 7.4 jest już trudne: według danych WordPress.org z czerwca 2026, udział PHP 7.0-7.1 wynosi mniej niż 0,3% wszystkich instalacji.
OpenSSL czy Sodium: dwa kryteria wyboru
Wybór między dwoma podejściami sprowadza się do dwóch czynników:
- Wersja PHP. PHP 7.2+, proszę brać Sodium, jest on bezpieczniejszy domyślnie i nie pozwala popełnić błędu przy implementacji HMAC. PHP 7.0-7.1, tylko OpenSSL. Poniżej PHP 7.0, czas zaktualizować serwer, a nie wymyślać protezy z PECL-mcrypt.
- Przenośność danych między środowiskami. OpenSSL jest dostępny wszędzie, włącznie z PHP 5.3+. Jeśli zaszyfrowane pliki mają być odczytywane i na serwerze deweloperskim, i na produkcji, i u klienta, OpenSSL jest pewniejszy z punktu widzenia kompatybilności. Szyfrogram Sodium odszyfruje się tylko tam, gdzie jest Sodium.
W praktyce używamy OpenSSL w projektach, gdzie ważna jest kompatybilność między różnymi środowiskami. Sodium tam, gdzie cały stos zaktualizowano do PHP 8 i bezpieczeństwo jest na pierwszym miejscu.
W filmie Dave Hollingworth szczegółowo omawia oba podejścia z demonstracją kodu i wyjaśnieniem prymitywów kryptograficznych stojących za każdym z nich. Materiał uzupełnia artykuł: pokazano przypadki brzegowe, instalację biblioteki defuse/php-encryption przez Composera i porównanie wydajności OpenSSL i Sodium na rzeczywistych danych.
⁉️🤔 Często zadawane pytania
Czy można użyć md5() lub sha1() do szyfrowania plików?
Nie.
md5()isha1()to funkcje skrótu, są one z definicji nieodwracalne. Zaszyfrowany plik musi dać się odszyfrować z powrotem, a skrótu odszyfrować nie można. Skróty stosuje się do weryfikacji integralności (jak HMAC w kodzie powyżej) i przechowywania haseł przezpassword_hash(), ale nie do szyfrowania zawartości.
Co zrobić, jeśli klucz szyfrowania został utracony?
Odszyfrowanie danych bez klucza jest niemożliwe. Proszę przechowywać klucz w
wp-config.phppoza document root i robić jego kopię zapasową oddzielnie od kopii plików i bazy danych. Proszę nie umieszczać klucza w repozytorium Git, dodaćwp-config.phpdo.gitignorelub używać zmiennych środowiskowych.
Po co jest IV, skoro klucz i tak jest tajny?
Bez losowego IV te same dane zaszyfrowane tym samym kluczem dają ten sam szyfrogram. Napastnik, widząc powtarzające się bloki, uzyskuje informację o strukturze pliku. IV czyni każde uruchomienie szyfrowania unikalnym: ten sam plik zaszyfrowany dwukrotnie tym samym kluczem daje dwa różne szyfrogramy.
Czy można szyfrować duże pliki, kilkugigabajtowe?
Powyższe funkcje wczytują plik do pamięci w całości, dla gigabajtowych danych doprowadzi to do wyczerpania pamięci. Do szyfrowania strumieniowego proszę użyć
openssl_encrypt()w pętli z buforowaniem fragmentami (np. po 1 MB) lub bibliotekidefuse/php-encryption, która obsługuje tryb strumieniowy od razu.
Czy ten kod działa na PHP 8.3?
Tak. Zarówno OpenSSL, jak i Sodium są w pełni wspierane w PHP 8. Kod przetestowano na aktualnych wersjach PHP i nie używa on przestarzałych funkcji. Na PHP 8.3 kompatybilność jest zachowana, nie ma niekompatybilności wstecznych w rozszerzeniu OpenSSL.
Szyfrowanie plików w 2026 roku: praktyczny werdykt
Mcrypt odszedł z PHP i wyszło to na dobre. Dwa wbudowane narzędzia na zastępstwo, i bezpieczniejsze, i szybsze, i niewymagające tańców z PECL-em. OpenSSL działa wszędzie, Sodium jest prostszy i niezawodniejszy domyślnie.
Krótko mówiąc: nowy projekt na PHP 8, proszę startować z Sodium, kod wychodzi czystszy. Przenosi Pan stary kod z mcrypt, proszę przepisać na OpenSSL, jest on dostępny nawet na PHP 7.0. I główna zasada kryptografii: proszę przechowywać klucze oddzielnie od zaszyfrowanych danych. Utrata klucza = utrata danych i brute force tu nie pomoże.
Proszę zacząć od skryptu testowego na kopii pliku: upewnić się, że cykl „zaszyfrowałem → odszyfrowałem → bajt po bajcie się zgadza" działa bez błędów. A jaki sposób stosują Państwo w swoich projektach, OpenSSL, Sodium czy coś innego? Proszę napisać w komentarzach.



