Перейти к содержанию

Криптографические поля в ORM

Криптографические поля позволяют автоматически шифровать данные при записи в базу данных и расшифровывать их при чтении. Это избавляет от необходимости вручную реализовывать шифрование при работе с конфиденциальной информацией: токенами, ключами доступа, персональными данными.

В ORM есть два типа криптографических полей:

  • CryptoField — базовый тип, который обеспечивает шифрование и расшифровку,

  • SecretField — расширение CryptoField, которое автоматически генерирует случайное значение, если поле не заполнено при создании записи.

Если нужно только шифровать существующие данные — используйте CryptoField. Если требуется гарантированно иметь уникальное секретное значение для каждой записи — используйте SecretField.

Совет

Подробнее о типах полей в статье Концепция ORM

Настроить ключ шифрования

Ключ шифрования указывается в файле конфигурации ядра /bitrix/.settings.php. В новых дистрибутивах система генерирует ключ автоматически. Если ключа нет, добавьте его вручную. Без ключа криптографические поля работать не будут.

'crypto' => [
    'value' => [
        'crypto_key' => 'mysupersecretphrase',
    ],
    'readonly' => true,
],

Ключ может быть любой уникальной строкой. Рекомендуем создавать ключ длиной 32 символа из букв латинского алфавита и цифр. Можно сгенерировать с помощью \Bitrix\Main\Security\Random::getString(32).

Внимание

Не меняйте ключ после того, как зашифровали данные. Расшифровать их будет невозможно.

Проверить доступность шифрования

Перед тем как использовать криптографические поля убедитесь, что шифрование доступно. Метод CryptoField::cryptoAvailable() проверяет:

  • наличие ключа в .settings.php,

  • поддержку OpenSSL на сервере.

if (\Bitrix\Main\ORM\Fields\CryptoField::cryptoAvailable())
{
    // можно работать с CryptoField и SecretField
}

Проверку можно использовать в установщиках модулей и при миграциях. Это поможет избежать ошибок в окружениях, где шифрование не настроено.

Подготовить таблицы

Зашифрованные данные занимают больше места, чем исходные. Перед тем как начать использовать криптографические поля, убедитесь, что размер колонки в базе данных позволяет хранить зашифрованные значения.

Длина зашифрованного значения зависит от режима шифрования. Для режима CTR используйте формулу:

newlen = (len + 16 + 32) * 1.5

Для остальных режимов формула:

newlen = (len + (16 - len % 16) + 16 + 32) * 1.5
  • len — максимальная длина исходных данных в байтах.

  • 16 и 32 — размеры служебных блоков: вектор и хеш.

  • 1.5 — примерный коэффициент увеличения при кодировке base64.

Пример. Если поле занимает до 10 байт, минимальный размер колонки должен быть около 96 байт.

Поле CryptoField

Поле CryptoField автоматически шифрует значение при записи и расшифровывает его при чтении. Используйте его для защиты существующих данных без изменения логики работы с ними. Пустые значения не шифруются и сохраняются как есть.

Объявить поле в классе таблицы

DataManager описывает структуру таблицы в ORM. Чтобы зашифровать конкретную колонку, укажите для нее тип crypto в методе getMap().

'ISBN' => [
    'data_type' => 'crypto',
],

Напрямую указывать тип crypto можно только:

  • для новых таблиц и колонок,

  • колонок, в которых данные точно зашифрованы.

Внимание

Поле не может одновременно работать с зашифрованными и незашифрованными данными.

Поэтому в описании поля обычно используют проверку. Метод cryptoEnabled('Field') проверяет, включено ли шифрование для указанного поля.

  • true — используется тип crypto,

  • false — обычная строка.

Это позволяет переключаться между режимами, например, во время миграции.

final class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName()
    {
        return 'book';
    }

    public static function getMap()
    {
        return [
            'ID' => [
                'data_type' => 'integer',
                'primary' => true,
                'autocomplete' => true,
            ],
            // Используем динамический тип данных для колонки ISBN
            'ISBN' => [
                'data_type' => (static::cryptoEnabled('ISBN') ? 'crypto' : 'string'),
            ],
        ];
    }
}

В объектном стиле используйте атрибут crypto_enabled.

new \Bitrix\Main\ORM\Fields\CryptoField('ISBN', [
    'crypto_enabled' => static::cryptoEnabled('ISBN'),
]);

Включить шифрование

После того как таблица создана и все данные зашифрованы, нужно сообщить ORM, что колонка теперь зашифрована. Для этого вызовите метод enableCrypto().

BookTable::enableCrypto('ISBN');

Метод сохранит информацию о шифровании в служебных опциях таблицы. После этого метод cryptoEnabled('ISBN') будет возвращать true.

Если таблица только создается и в ней еще нет данных, сразу включите шифрование для нужной колонки в установщике модуля.

if (\Bitrix\Main\ORM\Fields\CryptoField::cryptoAvailable())
{
    BookTable::enableCrypto('ISBN');
}

После этого все операции вставки через DataManager будут автоматически шифровать значение ISBN.

Мигрировать существующие данные

Если в таблице уже есть незашифрованные записи:

  1. Проверьте доступность шифрования.

  2. Увеличьте размер колонки при необходимости.

  3. Создайте временный класс-наследник DataManager с типом crypto без проверки.

  4. Обновите записи, чтобы они перезаписались и зашифровались.

  5. Вызовите метод enableCrypto() оригинальной таблицы.

use Bitrix\Main\Application;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\CryptoField;

// Временный класс для принудительного шифрования при обновлении
class TempBookTable extends DataManager
{
    public static function getTableName()
    {
        return 'book';
    }

    public static function getMap()
    {
        return [
            'ID' => [
                'data_type' => 'integer',
                'primary' => true,
                'autocomplete' => true,
            ],
            'ISBN' => [
                'data_type' => 'crypto', // всегда шифруем
            ],
        ];
    }
}

if (CryptoField::cryptoAvailable())
{
    $connection = Application::getConnection();
    $result = $connection->query('SELECT ID, ISBN FROM book');

    // Перебираем все записи и обновляем ISBN через временный класс
    while ($row = $result->fetch())
    {
        // Передаем то же значение, но поле crypto зашифрует его
        TempBookTable::update($row['ID'], ['ISBN' => $row['ISBN']]);
    }
    // После того как все данные зашифрованы, включаем режим шифрования для оригинальной таблицы
    BookTable::enableCrypto('ISBN');
}

После этого все данные в колонке ISBN будут зашифрованы, и можно использовать обычный BookTable с проверкой cryptoEnabled.

Для больших таблиц выполняйте обновление частями и вызывайте enableCrypto() только после завершения обработки всех записей.

Внимание

Во время миграции ограничьте доступ к таблице, чтобы избежать чтения уже зашифрованных значений как обычных строк.

Поле SecretField

Поле SecretField наследует поведение CryptoField и добавляет автоматическую генерацию значения, если поле не заполнено. Поле гарантирует, что у каждой записи будет секретное значение.

  1. Перед шифрованием SecretField кодирует данные в base64.

  2. Затем шифрует данные как CryptoField.

  3. При чтении расшифровывает и декодирует из base64.

Объявить поле в классе таблицы

В карте таблицы объявите поле как SecretField. Вы можете задать длину генерируемого секрета через параметр secret_length. По умолчанию длина — 20 байт.

use Bitrix\Main\ORM\Fields\SecretField;

public static function getMap()
{
    return [
        // ...
        // другие поля
        // ...

        'API_TOKEN' => new SecretField('API_TOKEN', [
            'secret_length' => 32,
        ]),
    ];
}

Если при добавлении записи не указать значение для API_TOKEN, поле заполнится случайной строкой указанной длины. Если передать свое значение, оно сохранится в зашифрованном виде.

При чтении значение автоматически расшифровывается.

Включить шифрование

Как и для CryptoField, после создания таблицы или добавления поля с уже существующими данными, нужно включить шифрование с помощью enableCrypto().

if (\Bitrix\Main\ORM\Fields\CryptoField::cryptoAvailable())
{
    MyTable::enableCrypto('API_TOKEN');
}

Если таблица новая и пустая, вызовите enableCrypto() сразу после ее создания, например, в установщике модуля.