Обновил класс с помощью ИИ, получилось как-то так.
Вроде работает.
Документация по class_encryption.php
Современный криптографический helper для vBulletin 3.
Файл предоставляет четыре основных механизма:
Code:
vB_Encrypt
Симметричное шифрование + HMAC
vB_KeyExchange_X25519
Обмен ключами между двумя сторонами
vB_PublicKey_Encrypt
Шифрование публичным ключом
vB_Signature_Ed25519
Цифровые подписи
Также имеется внутренний вспомогательный класс:
Он используется для Base64URL-кодирования и служебных проверок Sodium.
1. Подключение
В vBulletin 3:
PHP Code:
require_once(DIR . '/includes/class_encryption.php');
Файл защищён от прямого выполнения через браузер.
2. Требования
Для полного функционала рекомендуется расширение PHP Sodium.
Проверка:
PHP Code:
extension_loaded('sodium');
Sodium используется для:
- XChaCha20-Poly1305
- X25519
- Sealed Boxes
- Ed25519
OpenSSL используется для AES-256-GCM и является fallback-вариантом симметричного шифрования.
Получить доступные методы:
PHP Code:
$methods = vB_Encrypt::get_available_methods();
print_r($methods);
Например:
Code:
Array
(
[0] => xchacha20poly1305
[1] => aes256gcm
)
Получить предпочтительный алгоритм:
PHP Code:
$method = vB_Encrypt::get_preferred_method();
При наличии Sodium:
vB_Encrypt
Основной класс для симметричного шифрования данных.
Подходит для хранения:
- API-токенов
- OAuth access/refresh tokens
- секретов внешних интеграций
- токенов ботов
- секретных настроек
- персональных данных
- конфиденциальных значений в БД
- зашифрованных backup-данных
Поддерживает:
Code:
XChaCha20-Poly1305
AES-256-GCM
HMAC-SHA256
HKDF-SHA256
AAD
Оба алгоритма шифрования являются authenticated encryption.
Это означает, что при расшифровке одновременно проверяется целостность данных.
Если ciphertext был изменён, расшифровка завершится ошибкой.
3. Генерация ключа
Рекомендуемый вариант:
PHP Code:
$key = vB_Encrypt::generate_key_base64();
echo $key;
Генерируется случайный 256-битный ключ.
Для постоянного использования его можно хранить, например, в config.php:
PHP Code:
$config['Crypto']['masterkey'] = 'BASE64URL_KEY_HERE';
Master key желательно хранить отдельно от базы данных.
Если злоумышленник украдёт только MySQL dump, наличие ключа вне базы не позволит ему автоматически расшифровать защищённые значения.
4. Использование Base64-ключа
PHP Code:
$crypto = new vB_Encrypt();
$crypto->set_base64_key(
$config['Crypto']['masterkey']
);
Именно этот способ рекомендуется для ключей, созданных:
PHP Code:
vB_Encrypt::generate_key_base64();
5. Raw key
Можно работать с бинарным 32-байтовым ключом:
PHP Code:
$key = vB_Encrypt::generate_key();
$crypto = new vB_Encrypt();
$crypto->set_raw_key($key);
Метод set_raw_key() требует ключ ровно 32 байта.
6. set_key()
Также можно передать секретный материал напрямую:
PHP Code:
$crypto = new vB_Encrypt('some-secret');
или:
PHP Code:
$crypto = new vB_Encrypt();
$crypto->set_key('some-secret');
Из переданного материала внутренний ключ выводится через HKDF-SHA256.
Это не механизм хранения пользовательских паролей.
Пароли пользователей должны обрабатываться через:
PHP Code:
password_hash();
password_verify();
7. Простое шифрование
PHP Code:
$key = vB_Encrypt::generate_key_base64();
$crypto = new vB_Encrypt();
$crypto->set_base64_key($key);
$encrypted = $crypto->encrypt(
'Очень секретный текст'
);
$decrypted = $crypto->decrypt(
$encrypted
);
После расшифровки:
Code:
$decrypted === 'Очень секретный текст'
8. Автоматический выбор алгоритма
Обычный вызов:
PHP Code:
$encrypted = $crypto->encrypt($data);
Использует автоматический выбор:
Code:
есть XChaCha20-Poly1305
|
+--> используется XChaCha20-Poly1305
иначе
есть AES-256-GCM
|
+--> используется AES-256-GCM
иначе
|
+--> RuntimeException
Обычно вручную выбирать алгоритм не требуется.
9. Принудительный выбор алгоритма
XChaCha20-Poly1305:
PHP Code:
$encrypted = $crypto->encrypt(
$data,
'',
vB_Encrypt::METHOD_XCHACHA20_POLY1305
);
AES-256-GCM:
PHP Code:
$encrypted = $crypto->encrypt(
$data,
'',
vB_Encrypt::METHOD_AES_256_GCM
);
10. Формат ciphertext
Результат encrypt() является обычной ASCII-строкой.
Её можно хранить в:
- TEXT / MEDIUMTEXT
- файлах
- JSON
- API
- настройках форума
XChaCha20-Poly1305:
Code:
VBENC2.xchacha20poly1305.<nonce>.<ciphertext>
AES-256-GCM:
Code:
VBENC2.aes256gcm.<iv>.<tag>.<ciphertext>
Все бинарные части кодируются через Base64URL.
Алгоритм записан непосредственно в ciphertext, поэтому decrypt() самостоятельно определяет, какой метод использовать.
11. AAD — Additional Authenticated Data
AAD позволяет криптографически привязать ciphertext к определённому контексту.
Например:
PHP Code:
$aad = 'userid=123|field=api_token';
$encrypted = $crypto->encrypt(
$token,
$aad
);
Расшифровывать нужно с тем же AAD:
PHP Code:
$token = $crypto->decrypt(
$encrypted,
'userid=123|field=api_token'
);
Попытка использовать другой контекст:
PHP Code:
$crypto->decrypt(
$encrypted,
'userid=456|field=api_token'
);
завершится ошибкой аутентификации.
Практический вариант:
PHP Code:
$aad =
'table=user'
. '|userid=' . intval($userid)
. '|field=telegram_token';
$encrypted = $crypto->encrypt(
$telegram_token,
$aad
);
Теперь ciphertext криптографически привязан:
- к таблице
- к пользователю
- к назначению поля
AAD не является секретным и не шифруется.
12. Обработка ошибок расшифровки
PHP Code:
try
{
$plain = $crypto->decrypt(
$encrypted,
$aad
);
}
catch (Throwable $e)
{
// Неверный ключ
// Неверный AAD
// Повреждённый ciphertext
// Неподдерживаемый формат
// Authentication failure
}
Расшифровка не должна молча возвращать повреждённый plaintext.
При нарушении целостности возникает исключение.
13. HMAC-SHA256
Если данные скрывать не требуется, но необходимо защитить их от изменения, используется HMAC.
Создание подписи:
PHP Code:
$signature = $crypto->fetch_signature($data);
Проверка:
PHP Code:
if ($crypto->verify_signature($data, $signature))
{
// Подпись правильная
}
else
{
// Данные или подпись были изменены
}
Формат:
Используется:
14. Пример подписи API-запроса
PHP Code:
$payload = json_encode(array(
'userid' => 123,
'amount' => 500,
'timestamp' => time(),
));
$signature = $crypto->fetch_signature($payload);
На принимающей стороне:
PHP Code:
if (!$crypto->verify_signature($payload, $signature))
{
die('Invalid signature');
}
$data = json_decode($payload, true);
Если изменить даже одно поле payload, старая подпись больше не будет действительна.
Для защиты API от replay-атак рекомендуется включать в подписываемые данные:
- timestamp
- nonce
- transaction_id
- userid
- тип операции
vB_KeyExchange_X25519
Используется для установления общих session keys между двумя сторонами.
Например:
Code:
форум <-> внешний сервис
клиент <-> сервер
worker <-> API
Создание двух участников:
PHP Code:
$client = new vB_KeyExchange_X25519();
$server = new vB_KeyExchange_X25519();
15. Получение public key
PHP Code:
$client_public = $client->fetch_public_key();
$server_public = $server->fetch_public_key();
Public key можно свободно передавать другой стороне.
16. Получение session keys
Клиент:
PHP Code:
$client_keys = $client->fetch_client_session_keys(
$server->fetch_public_key()
);
Сервер:
PHP Code:
$server_keys = $server->fetch_server_session_keys(
$client->fetch_public_key()
);
Возвращаются:
PHP Code:
$client_keys['receive_key'];
$client_keys['send_key'];
$server_keys['receive_key'];
$server_keys['send_key'];
Соответствие:
Code:
client.send_key
=
server.receive_key
client.receive_key
=
server.send_key
Для каждого направления используется отдельный ключ.
17. X25519 + vB_Encrypt
Клиент:
PHP Code:
$client_keys = $client->fetch_client_session_keys(
$server_public
);
$client_crypto = new vB_Encrypt();
$client_crypto->set_base64_key(
$client_keys['send_key']
);
$ciphertext = $client_crypto->encrypt(
'Hello server'
);
Сервер:
PHP Code:
$server_keys = $server->fetch_server_session_keys(
$client_public
);
$server_crypto = new vB_Encrypt();
$server_crypto->set_base64_key(
$server_keys['receive_key']
);
$message = $server_crypto->decrypt(
$ciphertext
);
Результат:
Для обычных HTTP API X25519 не является заменой HTTPS/TLS.
Использовать его имеет смысл для собственных application-level протоколов и дополнительных криптографических схем.
18. Сохранение X25519 keypair
PHP Code:
$keypair = $client->fetch_keypair();
Позже объект можно восстановить:
PHP Code:
$client = new vB_KeyExchange_X25519(
$saved_keypair
);
Также доступны:
PHP Code:
$client->fetch_public_key();
$client->fetch_secret_key();
Secret key и keypair нельзя публиковать или записывать в открытые логи.
vB_PublicKey_Encrypt
Асимметричное шифрование через Sodium Sealed Boxes.
Принцип:
Code:
PUBLIC KEY
|
+--> позволяет шифровать
PRIVATE KEY / KEYPAIR
|
+--> позволяет расшифровать
Создание получателя:
PHP Code:
$recipient = new vB_PublicKey_Encrypt();
Получение public key:
PHP Code:
$public_key = $recipient->fetch_public_key();
Public key можно передавать всем отправителям.
19. Шифрование публичным ключом
Отправителю private key не требуется:
PHP Code:
$encrypted = vB_PublicKey_Encrypt::encrypt_for(
'Очень секретное сообщение',
$public_key
);
Формат:
Code:
VBSEAL1.<ciphertext>
20. Расшифровка
Расшифровать данные может владелец соответствующей keypair:
PHP Code:
$plain = $recipient->decrypt(
$encrypted
);
21. Практический сценарий Sealed Boxes
На публичном web-сервере хранится только public key:
PHP Code:
$PUBLIC_KEY = '...';
При поступлении секретных данных:
PHP Code:
$encrypted =
vB_PublicKey_Encrypt::encrypt_for(
$_POST['message'],
$PUBLIC_KEY
);
В базе хранится только:
Private key можно держать:
- на отдельном сервере
- в закрытом backend-процессе
- в специальном secret storage
- у администратора
Таким образом, публичный сервер умеет принимать и шифровать данные, но не обязан уметь расшифровывать уже сохранённые сообщения.
22. Сохранение keypair
PHP Code:
$keypair = $recipient->fetch_keypair();
Восстановление:
PHP Code:
$recipient = new vB_PublicKey_Encrypt(
$keypair
);
Также доступны:
PHP Code:
$recipient->fetch_public_key();
$recipient->fetch_secret_key();
vB_Signature_Ed25519
Предназначен для настоящих цифровых подписей.
В отличие от HMAC, проверяющей стороне не требуется знать секретный ключ.
Схема:
Code:
PRIVATE KEY
|
+--> создаёт подпись
PUBLIC KEY
|
+--> проверяет подпись
Подходит для:
- подписанных API-сообщений
- обновлений
- плагинов
- лицензионных файлов
- SSO-токенов
- межсерверных событий
- подписанных конфигураций
23. Создание Ed25519 keypair
PHP Code:
$signer = new vB_Signature_Ed25519();
Public key:
PHP Code:
$public_key = $signer->fetch_public_key();
Public key можно распространять.
Полная keypair:
PHP Code:
$keypair = $signer->fetch_keypair();
Её необходимо хранить секретно.
24. Создание подписи
PHP Code:
$message = 'userid=123|amount=500';
$signature = $signer->sign(
$message
);
Формат:
Code:
VBED25519.<signature>
25. Проверка Ed25519
Для проверки private key не требуется:
PHP Code:
$valid =
vB_Signature_Ed25519::verify(
$message,
$signature,
$public_key
);
if ($valid)
{
echo 'Signature valid';
}
Если данные изменить:
PHP Code:
$valid =
vB_Signature_Ed25519::verify(
'userid=123|amount=500000',
$signature,
$public_key
);
результат будет:
26. Восстановление Ed25519 keypair
Сохранение:
PHP Code:
$keypair = $signer->fetch_keypair();
Восстановление:
PHP Code:
$signer = new vB_Signature_Ed25519(
$keypair
);
27. Важное правило для постоянных keypair
Следующие вызовы без аргументов создают новые ключевые пары:
PHP Code:
new vB_KeyExchange_X25519();
new vB_PublicKey_Encrypt();
new vB_Signature_Ed25519();
Если серверу нужна постоянная криптографическая идентичность, нельзя генерировать новую keypair при каждом HTTP-запросе.
Правильно:
PHP Code:
// Создать один раз
$signer = new vB_Signature_Ed25519();
$keypair = $signer->fetch_keypair();
// Сохранить $keypair
При следующих запросах:
PHP Code:
$signer = new vB_Signature_Ed25519(
$stored_keypair
);
28. Где хранить ключи
Для vB_Encrypt:
Code:
MASTER KEY
|
+--> config.php / ENV / secret storage
CIPHERTEXT
|
+--> MySQL
Не рекомендуется хранить master key рядом с ciphertext в той же таблице базы данных.
Для Ed25519:
Code:
PRIVATE KEY / KEYPAIR
|
+--> секретное хранилище
PUBLIC KEY
|
+--> можно свободно распространять
Для PublicKey Encryption:
Code:
PRIVATE KEY / KEYPAIR
|
+--> секретное хранилище
PUBLIC KEY
|
+--> всем отправителям
29. Обработка исключений
Рекомендуемый production-код:
PHP Code:
try
{
$plain = $crypto->decrypt(
$encrypted,
$aad
);
}
catch (InvalidArgumentException $e)
{
// Некорректный формат
// Некорректный ключ
// Некорректные входные данные
}
catch (RuntimeException $e)
{
// Ошибка crypto backend
// Authentication failure
// Неправильный ключ
// Повреждённый ciphertext
}
Не рекомендуется показывать подробные сообщения исключений конечному пользователю.
Технические подробности лучше писать в серверный лог.
Краткий справочник API
vB_Encrypt
PHP Code:
new vB_Encrypt($key = null);
set_key($key);
set_raw_key($key);
set_base64_key($key);
generate_key();
generate_key_base64();
get_available_methods();
get_preferred_method();
encrypt(
$plain,
$aad = '',
$method = vB_Encrypt::METHOD_AUTO
);
decrypt(
$ciphertext,
$aad = ''
);
fetch_signature($data);
verify_signature(
$data,
$signature
);
Константы:
PHP Code:
vB_Encrypt::METHOD_AUTO
vB_Encrypt::METHOD_XCHACHA20_POLY1305
vB_Encrypt::METHOD_AES_256_GCM
vB_KeyExchange_X25519
PHP Code:
new vB_KeyExchange_X25519(
$encodedKeypair = ''
);
fetch_keypair();
fetch_public_key();
fetch_secret_key();
fetch_client_session_keys(
$serverPublicKey
);
fetch_server_session_keys(
$clientPublicKey
);
vB_PublicKey_Encrypt
PHP Code:
new vB_PublicKey_Encrypt(
$encodedKeypair = ''
);
fetch_keypair();
fetch_public_key();
fetch_secret_key();
vB_PublicKey_Encrypt::encrypt_for(
$plainData,
$recipientPublicKey
);
decrypt(
$encryptedData
);
vB_Signature_Ed25519
PHP Code:
new vB_Signature_Ed25519(
$encodedKeypair = ''
);
fetch_keypair();
fetch_public_key();
fetch_secret_key();
sign($data);
vB_Signature_Ed25519::verify(
$data,
$signature,
$publicKey
);
Какой механизм выбирать
Code:
Нужно скрыть данные и позже расшифровать их тем же сервером?
vB_Encrypt
Нужно защитить открытые данные от изменения,
и обе стороны могут знать общий секрет?
HMAC:
vB_Encrypt::fetch_signature()
vB_Encrypt::verify_signature()
Нужно подписывать одной стороной,
а проверять множеством систем,
не выдавая им private key?
vB_Signature_Ed25519
Любой отправитель должен иметь возможность
зашифровать сообщение,
но расшифровать его должен только владелец private key?
vB_PublicKey_Encrypt
Два сервиса должны договориться
о временных session keys?
vB_KeyExchange_X25519
Практический пример для vBulletin: шифрование API-токена
В config.php:
PHP Code:
$config['Crypto']['masterkey'] =
'BASE64URL_MASTER_KEY_HERE';
Создать master key можно один раз:
PHP Code:
echo vB_Encrypt::generate_key_base64();
При сохранении API-токена:
PHP Code:
require_once(DIR . '/includes/class_crypto.php');
$crypto = new vB_Encrypt();
$crypto->set_base64_key(
$config['Crypto']['masterkey']
);
$aad =
'integration=boosty'
. '|userid=' . intval($userid);
$encrypted_token =
$crypto->encrypt(
$access_token,
$aad
);
В MySQL сохраняется строка:
Code:
VBENC2.xchacha20poly1305....
При чтении:
PHP Code:
$access_token =
$crypto->decrypt(
$encrypted_token,
$aad
);
Что этим классом делать не нужно
1. Не шифровать пользовательские пароли.
Неправильно:
PHP Code:
$encrypted_password =
$crypto->encrypt($password);
Пароли должны храниться через необратимое password hashing.
2. Не использовать X25519 как самодельную замену HTTPS/TLS.
Для обычного HTTP API должен использоваться HTTPS.
3. Не публиковать:
Code:
fetch_secret_key()
fetch_keypair()
master encryption key
4. Не записывать секретные ключи в debug output, HTML или обычные application logs.
5. Не генерировать новую постоянную keypair при каждом запросе, если ожидается постоянная идентичность сервера.
Минимальная памятка
Симметричное шифрование:
PHP Code:
$key = vB_Encrypt::generate_key_base64();
$c = new vB_Encrypt();
$c->set_base64_key($key);
$enc = $c->encrypt('secret');
$dec = $c->decrypt($enc);
HMAC:
PHP Code:
$sig = $c->fetch_signature('data');
$ok = $c->verify_signature(
'data',
$sig
);
Public-key encryption:
PHP Code:
$r = new vB_PublicKey_Encrypt();
$enc =
vB_PublicKey_Encrypt::encrypt_for(
'secret',
$r->fetch_public_key()
);
$dec = $r->decrypt($enc);
Ed25519:
PHP Code:
$s = new vB_Signature_Ed25519();
$sig = $s->sign('data');
$ok =
vB_Signature_Ed25519::verify(
'data',
$sig,
$s->fetch_public_key()
);
X25519:
PHP Code:
$client = new vB_KeyExchange_X25519();
$server = new vB_KeyExchange_X25519();
$client_keys =
$client->fetch_client_session_keys(
$server->fetch_public_key()
);
$server_keys =
$server->fetch_server_session_keys(
$client->fetch_public_key()
);
Соответствие session keys:
Code:
$client_keys['send_key']
===
$server_keys['receive_key']
$client_keys['receive_key']
===
$server_keys['send_key']