Криптографические поля в 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() сразу после ее создания, например, в установщике модуля.

Следующая