Почему нельзя фильтровать по CryptoField и как это обойти
Проблема/контекст
CryptoField шифрует данные при записи и расшифровывает при чтении. Однако каждый вызов encrypt() в Bitrix\Main\Security\Cipher генерирует случайный вектор инициализации (IV). Одно и то же значение при каждом шифровании превращается в разный шифротекст. Это значит, что фильтрация через getList() по зашифрованному полю не вернет результатов: SQL-запрос сравнит открытый текст из фильтра с шифротекстом в базе и ничего не найдет.
На практике проблема возникает, как только нужно найти запись по API-токену, ключу доступа или другому зашифрованному идентификатору — задача, которая появляется в любой интеграции.
Решение с кодом
Добавьте к таблице индексируемую колонку с хешем открытого значения. Фильтруйте по хешу, а зашифрованное поле используйте только для чтения.
<?php
declare(strict_types=1);
namespace Vendor\Integration\Model;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields;
use Bitrix\Main\Security\Random;
final class ApiTokenTable extends DataManager
{
public static function getTableName(): string
{
return 'vendor_api_tokens';
}
public static function getMap(): array
{
return [
new Fields\IntegerField('ID', [
'primary' => true,
'autocomplete' => true,
]),
new Fields\IntegerField('USER_ID', [
'required' => true,
]),
// Зашифрованный токен — только для хранения и чтения
new Fields\CryptoField('TOKEN', [
'crypto_enabled' => static::cryptoEnabled('TOKEN'),
]),
// SHA-256 хеш токена — для поиска по индексу
new Fields\StringField('TOKEN_HASH', [
'size' => 64,
]),
];
}
// Создает запись с новым токеном
public static function issueToken(int $userId): array
{
$token = Random::getString(32);
$result = static::add([
'USER_ID' => $userId,
'TOKEN' => $token,
'TOKEN_HASH' => hash('sha256', $token),
]);
return ['id' => $result->getId(), 'token' => $token];
}
// Находит запись по открытому токену через хеш
public static function findByToken(string $token): ?array
{
return static::getRow([
'filter' => ['=TOKEN_HASH' => hash('sha256', $token)],
]);
}
}
Схема: при создании записи токен шифруется CryptoField и сохраняется в TOKEN, а его SHA-256 хеш записывается в TOKEN_HASH. При входящем запросе вычисляется хеш и выполняется поиск по индексированной колонке. Исходный токен восстанавливается из зашифрованного поля автоматически при чтении.
Размер колонки TOKEN должен учитывать увеличение данных при шифровании. Для 32-байтного токена в режиме CTR минимальный размер: (32 + 16 + 32) * 1.5 ≈ 120 байт. Используйте VARCHAR(255). Колонка TOKEN_HASH фиксированная — CHAR(64) с уникальным индексом.
Еще одна деталь, которую стоит учитывать: если шифрование завершится ошибкой — например, ключ не настроен в .settings.php — метод encrypt() возвращает null, а не исходное значение. Данные молча теряются. Поэтому всегда проверяйте доступность шифрования через CryptoField::cryptoAvailable() перед включением криптополей, как это делается в ядре — например, в UserPhoneAuthTable для OTP-секретов.
Паттерн похож на хранение паролей, но с важным отличием: здесь исходное значение нужно восстановить, поэтому вместо необратимого bcrypt используется обратимое шифрование AES-256-CTR через CryptoField, а хеш служит только для поиска.
Итог
CryptoField защищает данные в хранилище, но не предназначен для поиска. Если по зашифрованному полю нужна фильтрация, дублируйте значение хешем и ищите по нему.
