Реализация шифрования в PHP: от простых до сложных примеров
В веб-разработке шифрование данных является обязательным требованием для защиты конфиденциальной информации. PHP предоставляет несколько инструментов для этой задачи, от устаревшего mcrypt до современных расширений openssl и sodium. В статье рассмотрены наиболее актуальные подходы, их сильные стороны и подводные камни.
Основные методы шифрования в PHP
Cовременное симметричное шифрование с использованием Sodium (libsodium)
Расширение Sodium (встроено в PHP 7.2+) является рекомендуемым решением для симметричного шифрования. Оно предоставляет простой API с встроенным управлением ключами, nonce и аутентификацией.
Как организовать шифрование данных без лишних усилий и с максимальной безопасностью?
<?php
// Генерация ключа (секретного)
$key = sodium_crypto_secretbox_keygen();
// Неповторяющийся nonce (24 байта)
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$message = 'Это секретные данные';
// Шифрование
$ciphertext = sodium_crypto_secretbox($message, $nonce, $key);
// Расшифровка
$decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
if ($decrypted === false) {
// данные повреждены или ключ неверен
}
?>
Access php (доступ к файлам в php)
Возможные проблемы:
- Nonce не должен повторяться при одном и том же ключе: всегда использовать random_bytes.
- Ключ необходимо хранить в безопасном месте, не в коде.
- При повреждении ciphertext расшифровка вернёт false – предусмотреть проверку.
Использование Sodium исключает типичные ошибки (неправильная длина IV, отсутствие аутентификации), что делает его основным выбором для новых проектов.
Как выполнить симметричное шифрование с помощью OpenSSL в режиме AEAD (AES-256-GCM)?
OpenSSL поддерживает аутентифицированное шифрование с дополнительными данными (AEAD). Режим GCM обеспечивает как конфиденциальность, так и целостность.
<?php
$plaintext = 'Секрет';
$key = openssl_random_pseudo_bytes(32); // AES-256
$ivlen = openssl_cipher_iv_length('aes-256-gcm'); // 12 байт
$iv = openssl_random_pseudo_bytes($ivlen);
$tag = ''; // будет заполнено шифрованием
$ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '', 16);
// Расшифровка
$decrypted = openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
?>
Php filter (фильтрация данных в php)
Типичные ошибки:
- Не сохранён тег аутентификации ($tag). Без него расшифровка становится небезопасной.
- Размер IV фиксирован: для GCM 12 байт, использование других размеров приводит к ошибкам.
- Забывают передать OPENSSL_RAW_DATA, и данные кодируются в base64.
Как защитить данные с помощью шифрования на основе пароля (password-based encryption)?
Для хранения данных, доступных только владельцу пароля, применяется вывод ключа из пароля (PBKDF2 или Argon2) и последующее шифрование.
<?php
$password = 'пароль';
$salt = random_bytes(16);
$key = hash_pbkdf2('sha256', $password, $salt, 100000, 32, true);
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
$ciphertext = openssl_encrypt('конфиденциально', 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
// Для проверки целостности - добавить HMAC
$hmac = hash_hmac('sha256', $ciphertext, $key, true);
?>
Php пароль (работа с паролями в php)
Проблемы и их решения:
- Отсутствие аутентификации: режим CBC не защищает от подмены. После расшифровки проверять HMAC.
- Слабый пароль: используйте Argon2id (функция sodium_crypto_pwhash) вместо PBKDF2.
- Не хранить соль и nonce вместе с зашифрованными данными – это нормальная практика, но они не должны быть секретными.
Как использовать асимметричное шифрование в PHP (RSA с openssl)?
Когда требуется обмен зашифрованными данными без общего секретного ключа, применяют публичный и приватный ключи.
<?php
// Генерация пары ключей
$config = array(
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA
);
$res = openssl_pkey_new($config);
openssl_pkey_export($res, $privateKey);
$publicKey = openssl_pkey_get_details($res)['key'];
// Шифрование открытым ключом
$plaintext = 'Секрет для получателя';
openssl_public_encrypt($plaintext, $ciphertext, $publicKey);
// Расшифровка закрытым ключом
openssl_private_decrypt($ciphertext, $decrypted, $privateKey);
?>
Tokens php (токены в php)
Ограничения RSA:
- Максимальная длина открытого текста зависит от размера ключа (2048 бит ~ 245 байт). Для больших данных использовать гибридное шифрование (шифрование симметричным ключом, который зашифрован RSA).
- Отсутствие встроенной аутентификации – можно подменить зашифрованное сообщение. Добавлять подпись.
- Производительность RSA ниже, чем у симметричных алгоритмов.
Как использовать готовую библиотеку defuse/php-encryption для упрощения?
Библиотека defuse/php-encryption (на основе openssl и хэшей) предоставляет простой интерфейс для шифрования строк, автоматически обрабатывая nonce, аутентификацию и управление ключами.
// composer require defuse/php-encryption
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
$key = Key::createNewRandomKey();
$ciphertext = Crypto::encrypt('Сообщение', $key);
$plaintext = Crypto::decrypt($ciphertext, $key);
Особенности:
- Библиотека генерирует случайный nonce и добавляет HMAC, что упрощает код.
- Ключ должен быть сохранён в виде строки (Key::saveToAsciiSafeString()).
- При изменении версии библиотеки формат может измениться – учитывать.
Дополнительные примеры демонстрируют менее очевидные сценарии и полную отладку результатов.
Расширенные примеры с выводом
Пример 1. Шифрование файла с помощью Sodium (чтение, шифрование, запись)
<?php
$inputFile = 'secret.txt';
$outputFile = 'secret.enc';
$key = sodium_crypto_secretbox_keygen(); // сохранить в безопасном месте
$plaintext = file_get_contents($inputFile);
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
file_put_contents($outputFile, $nonce . $ciphertext);
echo "Файл зашифрован: $outputFile\n";
// Вывод: Файл зашифрован: secret.enc
// Расшифровка
$encData = file_get_contents($outputFile);
$nonce = substr($encData, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = substr($encData, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
if ($decrypted === false) {
echo "Ошибка расшифровки\n";
} else {
file_put_contents('secret_dec.txt', $decrypted);
echo "Файл расшифрован в secret_dec.txt\n";
}
?>
Файл зашифрован: secret.enc Файл расшифрован в secret_dec.txt
Пример 2. Гибридное шифрование (RSA + AES) для передачи больших сообщений
<?php
// Предполагается, что открытый ключ получателя известен
$publicKey = '-----BEGIN PUBLIC KEY-----\n...';
$message = 'Длинный текст, который не влезет в RSA за раз';
// 1. Генерация случайного симметричного ключа
$symmetricKey = random_bytes(32); // AES-256
// 2. Шифрование сообщения симметричным ключом (AES-256-GCM)
$iv = random_bytes(12);
$tag = '';
$encMessage = openssl_encrypt(
$message, 'aes-256-gcm', $symmetricKey, OPENSSL_RAW_DATA, $iv, $tag, '', 16
);
// 3. Шифрование симметричного ключа открытым ключом RSA
openssl_public_encrypt(
$symmetricKey, $encKey, $publicKey, OPENSSL_PKCS1_OAEP_PADDING
);
// 4. Передача: ($encKey, $iv, $tag, $encMessage)
// Получатель:
// - расшифровывает $encKey своим закрытым ключом
// - использует $symmetricKey, $iv, $tag для расшифровки $encMessage
?>
(нет вывода, только логика)
Пример 3. Шифрование сериализованных данных (массив/объект) с сохранением структуры
<?php
$data = ['user_id' => 42, 'role' => 'admin', 'expires' => time() + 3600];
$serialized = serialize($data);
$key = sodium_crypto_secretbox_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$encrypted = sodium_crypto_secretbox($serialized, $nonce, $key);
// Можно хранить в сессии или куки
$storable = base64_encode($nonce . $encrypted);
// Расшифровка
$decoded = base64_decode($storable);
$nonce = substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$decSerial = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
$decData = unserialize($decSerial);
print_r($decData);
?>
Array
(
[user_id] => 42
[role] => admin
[expires] => 1718123456
)
Пример 4. Использование sodium_crypto_aead для аутентифицированного шифрования с дополнительными данными (AAD)
<?php
$key = sodium_crypto_aead_xchacha20poly1305_ietf_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
$plaintext = 'Данные';
$aad = 'метаданные-заголовок'; // не шифруется, но аутентифицируется
$ciphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
$plaintext, $aad, $nonce, $key
);
// При расшифровке нужно передать тот же AAD
$decrypted = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
$ciphertext, $aad, $nonce, $key
);
echo $decrypted; // Данные
?>
Данные
Пример 5. Шифрование базы данных на PHP с использованием Sodium (для защиты полей)
<?php
class ColumnEncryptor
{
private string $key;
public function __construct(string $key)
{
$this->key = $key;
}
public function encrypt(string $value): string
{
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox($value, $nonce, $this->key);
return base64_encode($nonce . $ciphertext);
}
public function decrypt(string $encoded): ?string
{
$decoded = base64_decode($encoded, true);
if ($decoded === false) return null;
$nonce = substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$plain = sodium_crypto_secretbox_open($ciphertext, $nonce, $this->key);
return $plain === false ? null : $plain;
}
}
$encryptor = new ColumnEncryptor(sodium_crypto_secretbox_keygen());
$enc = $encryptor->encrypt('конфиденциальный email@example.com');
echo $enc . "\n";
echo $encryptor->decrypt($enc) . "\n";
?>
K25ub25jZUxhcmdl... (строка base64) конфиденциальный email@example.com