Шифрование Active Record

Это руководство раскрывает шифрование информации вашей базы данных с помощью Active Record.

После его прочтения, вы узнаете:

  • Как настроить шифрование базы данных с помощью Active Record.
  • Как мигрировать незашифрованные данные
  • Как обеспечить сосуществование различных схем шифрования
  • Как использовать API
  • Как настроить библиотеку, и как расширить ее

Active Record предоставляет шифрование на уровне приложения. Он работает, объявляя какие атрибуты должны быть зашифрованы, и бесшовно шифрует и дешифрует их по необходимости. Уровень шифрования помещен между базой данных и приложением. У приложения будет доступ к незашифрованным данным, но база данных будет хранить их зашифрованными.

1. Базовое использование

1.1. Настройка

Сначала необходимо добавить несколько ключей в ваши учетные данные rails. Запустите bin/rails db:encryption:init для генерации набора случайных ключей:

$ bin/rails db:encryption:init
Add this entry to the credentials of the target environment:

active_record_encryption:
  primary_key: EGY8WhulUOXixybod7ZWwMIL68R9o5kC
  deterministic_key: aPA5XyALhf75NNnMzaspW7akTfZp0lPY
  key_derivation_salt: xEY0dt6TZcAMg52K7O84wYzkjvbA62Hz

Эти сгенерированные ключи и соль длиной 32 байта. Если вы генерируете их самостоятельно, минимальная длина, которую следует использовать, это 12 байт для главного ключа (он будет использоваться для получения 32-байтного ключа AES) и 20 байт для соли.

1.2. Объявление шифруемых атрибутов

Шифруемые атрибуты определяются на уровне модели. Это обычные атрибуты Active Record на основе столбца с тем же именем.

class Article < ApplicationRecord
  encrypts :title
end

Библиотека прозрачно зашифрует эти атрибуты перед сохранением в базу данных и дешифрует при извлечении их значений:

article = Article.create title: "Encrypt it all!"
article.title # => "Encrypt it all!"

Но под капотом выполняемый SQL будет выглядеть так:

INSERT INTO `articles` (`title`) VALUES ('{\"p\":\"n7J0/ol+a7DRMeaE\",\"h\":{\"iv\":\"DXZMDWUKfp3bg/Yu\",\"at\":\"X1/YjMHbHD4talgF9dt61A==\"}}')

Шифрование занимает дополнительное место в этом столбце. Можно оценить верхний предел перегрузки примерно в 250 байтов, когда используется встроенный провайдер ключа конвертного шифрования (envelope encryption). Для средних и больших текстовых столбцов эта перегрузка незначительна, но для столбцов string из 255 байтов следует увеличить их лимит (рекомендуется 510).

Причиной для дополнительного пространства являются шифрование Base 64 и дополнительные метаданные, хранимые вместе с зашифрованными значениями.

1.3. Детерминированное и недетерминированное шифрование

По умолчанию Active Record Encryption использует недетерминированный подход к шифрованию. Это означает, что шифрование одного и того же содержимого с тем же самым паролем дважды приведет к различным зашифрованным текстам. Это хорошо для безопасности, поскольку делает криптоанализ зашифрованного содержимого гораздо сложнее, но это делает невозможным запросы к базе данных.

Можно использовать опцию deterministic: для генерации векторов инициализации детерминированным образом, эффективно включая запросы по зашифрованным данным.

class Author < ApplicationRecord
  encrypts :email, deterministic: true
end

Author.find_by_email("some@email.com") # Можно делать запросы, как обычно

Рекомендовано использовать значение по умолчанию (недетерминированное), если вам не нужно делать запросы по данным.

В недетерминированном режиме используется AES-GCM с 256-битным ключом и случайным вектором инициализации. В детерминированном режиме тоже используется AES-GCM, но вектор инициализации генерируется как дайджест HMAC-SHA-256 ключа и шифруемого содержимого.

Можно отключить детерминированное шифрования, просто не настраивая deterministic_key.

2. Особенности

2.1. Action Text

Можно шифровать атрибуты action text, передав encrypted: true в их объявлении.

class Message < ApplicationRecord
  has_rich_text :content, encrypted: true
end

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

2.2. Фикстуры

Можно сделать фикстуры Rails шифруемыми автоматически, добавив эту опцию в ваш test.rb:

config.active_record.encryption.encrypt_fixtures = true

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

2.2.1. Фикстуры Action text

Чтобы зашифровать фикстуры action text, следует поместить их в fixtures/action_text/encrypted_rich_texts.yml.

2.3. Поддерживаемые типы

active_record.encryption будет сериализовывать значения с помощью лежащего в основе типа до их шифрования, но они должны быть сериализуемыми как строки. Структурируемые типы, наподобие serialized, поддерживаются из коробки.

Если нужна поддержка пользовательского типа, рекомендованным способом является использование сериализуемого атрибута. Объявление сериализуемого атрибута должно идти перед объявлением шифрования:

# ХОРОШО
class Article < ApplicationRecord
  serialize :title, Title
  encrypts :title
end

# ПЛОХО
class Article < ApplicationRecord
  encrypts :title
  serialize :title, Title
end

2.4. Игнорирование регистра

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

Можно использовать опцию :downcase при объявлении шифруемого атрибута. Это сделает его содержимое в нижнем регистре до шифрования.

class Person
  encrypts :email_address, deterministic: true, downcase: true
end

При использовании :downcase оригинальный регистр теряется. Иногда необходимо сохранить оригинальный регистр при чтении значения, но нужно игнорировать режим при поиске. Для таких случаев можно использовать опцию :ignore_case, требующую добавления нового столбца с именем original_<column_name>, для хранения содержимого с неизмененным регистром:

class Label
  encrypts :name, deterministic: true, ignore_case: true # содержимое с оригинальным регистром будет храниться в столбце `original_name`
end

2.5. Поддержка нешифрованных данных

Чтобы облегчить миграции нешифрованных данных, библиотека включает опцию config.active_record.encryption.support_unencrypted_data. Когда установлена true:

  • Пытается прочитать шифруемые атрибуты, которые не были зашифрованы, будет работать нормально, без вызова какой-либо ошибки
  • Запросы с детерминировано шифруемыми атрибутами будут включать версию с исходным тестом, чтобы поддержать и зашифрованное, и незашифрованное содержимое. Необходимо установить config.active_record.encryption.extend_queries = true чтобы включить это.

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

2.6. Поддержка предыдущих схем шифрования

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

Чтобы поддержать такие ситуации, можно объявить предыдущие схемы шифрования, которые будут использованы в двух сценариях:

  • При чтении шифрованных данных Active Record Encryption попробует предыдущие схемы шифрования, если текущая схема не сработает.
  • При запросе детерминированных данных, он добавит в запросы шифры с использованием предыдущих схем, таким образом, запросы будут работать бесшовно с разными схемами. Чтобы включить это, необходимо установить config.active_record.encryption.extend_queries = true.

Можно настроить предыдущие схемы шифрования:

  • Глобально
  • На основе атрибута
2.6.1. Глобальные предыдущие схемы шифрования

Можно добавить предыдущие схемы шифрования, добавив их как список свойств с помощью конфигурационной настройки previous в application.rb:

config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
2.6.2. Схемы шифрования для атрибута

Используйте :previous при объявлении атрибута:

class Article
  encrypts :title, deterministic: true, previous: { deterministic: false }
end
2.6.3. Схемы шифрования и детерминированные атрибуты

При добавлении предыдущих схем шифрования:

  • При недетерминированном шифровании, новая информация всегда будет шифроваться с помощью новейшей (текущей) схемы шифрования.
  • При детерминированном шифровании, новая информация по умолчанию всегда будет шифроваться с помощью старейшей схемы шифрования.

Причиной для этого, при недетерминированном шифровании, является то, что мы обычно хотим, чтобы шифры оставались постоянными. Это поведение можно изменить, установив deterministic: { fixed: false }. В этом случае она будет использовать новейшую схему шифрования для шифрования новых данных.

2.7. Ограничения уникальности

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

2.7.1. Валидации уникальности

Валидации уникальности работают нормально, когда включены расширенные запросы (config.active_record.encryption.extend_queries = true).

class Person
  validates :email_address, uniqueness: true
  encrypts :email_address, deterministic: true, downcase: true
end

Они также будут работать при комбинации шифрованных и нешифрованных данных и когда настроены предыдущие схемы шифрования.

Если хотите игнорировать регистр, убедитесь что используются downcase: или ignore_case: в объявлении encrypts. Использование опции case_sensitive: в валидации не будет работать.

2.7.2. Индексы уникальности

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

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

class Person
  encrypts :email_address, deterministic: true
end

2.8. Фильтрация параметров, названных как шифруемые столбцы

По умолчанию шифруемые столбцы настраиваются как автоматически фильтруемые в логах Rails. Это поведение можно отключить, добавив следующее в application.rb:

config.active_record.encryption.add_to_filter_parameters = false

В случае, если хотите исключить отдельные столбцы из этой автоматической фильтрации, добавьте их в config.active_record.encryption.excluded_from_filter_parameters.

3. Управление ключами managemen

Стратегии управления ключами реализованы провайдерами ключа. Можно настроить провайдеры ключа глобально или на основе атрибута.

3.1. Встроенные провайдеры ключа

3.1.1. DerivedSecretKeyProvider

Провайдер ключа, который отдает ключи, производные от предоставленных паролей, с помощью PBKDF2.

config.active_record.encryption.key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(["some passwords", "to derive keys from. ", "These should be in", "credentials"])

По умолчанию active_record.encryption настраивает DerivedSecretKeyProvider с помощью ключей, определенных в active_record.encryption.master_key.

3.1.2. EnvelopeEncryptionKeyProvider

Реализует простую стратегию конвертного шифрования:

  • Он генерирует случайный ключ для каждой операции шифрования данных
  • Он хранит ключ вместе с самими данными, зашифрованными с помощью главного ключа в учетных данных active_record.encryption.master_key.

Можно настроить, добавив следующее в ваш application.rb:

config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new

Как и для других встроенных провайдеров ключей, можно предоставить список главных ключей в active_record.encryption.master_key для реализации схем ротации ключа.

3.2. Пользовательские провайдеры ключа key providers

Для расширенных схем управления ключами можно настроить пользовательский провайдер ключа в инициализаторе:

ActiveRecord::Encryption.key_provider = MyKeyProvider.new

Провайдер ключа должен реализовывать этот интерфейс:

class MyKeyProvider
  def encryption_key
  end

  def decryption_keys(encrypted_message)
  end
end

Оба метода должны возвращать объекты ActiveRecord::Encryption::Key:

  • encryption_key возвращает ключ для шифрования некоторого содержимого
  • decryption keys возвращает список потенциальных ключей для дешифрования заданного сообщения

Ключ может включать произвольные теги, которые будет храниться нешифрованными с сообщением. Для просмотра этих значений можно использовать ActiveRecord::Encryption::Message#headers при дешифровке.

3.3. Провайдеры ключа, специфичные для модели

Можно настроить провайдер ключа на основе класса с помощью опции :key_provider:

class Article < ApplicationRecord
  encrypts :summary, key_provider: ArticleKeyProvider.new
end

3.4. Ключи, специфичные для модели

Можно настроить заданный ключ на основе класса с помощью опции :key:

class Article < ApplicationRecord
  encrypts :summary, key: "some secret key for article summaries"
end

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

3.5. Ротация ключей

active_record.encryption может работать со списками ключей для реализации схем ротации ключей:

  • Первый ключ будет использоваться для шифрования нового содержимого.
  • При дешифровании будут пробоваться все ключи, пока один не сработает.
active_record
  encryption:
    master_key:
        - bc17e7b413fd4720716a7633027f8cc4 # Активный, шифрует новое содержимое
        - a1cc4d7b9f420e40a337b9e68c5ecec6 # Предыдущие ключи все еще могут дешифровывать содержимое
    key_derivation_salt: a3226b97b3b2f8372d1fc6d497a0c0d3

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

Сейчас ротация ключей не поддерживается для детерминированного шифрования.

Active Record Encryption пока что не предоставляет автоматического управления над ротацией ключей. Все для этого уже есть, но пока не реализовано.

3.6. Хранение ссылок на ключ

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

config.active_record.encryption.store_key_references = true

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

4. API

4.1. Базовый API

Шифрование ActiveRecord подразумевает декларативное использование, но оно также предоставляет API для сценариев продвинутого использования.

4.1.1. Шифрование и дешифрование
article.encrypt # шифрует или перешифрует все шифруемые атрибуты
article.decrypt # дешифрует все шифруемые атрибуты
4.1.2. Чтение шифровки
article.ciphertext_for(:title)
4.1.3. Проверка, шифруется ли атрибут
article.encrypted_attribute?(:title)

5. Настройка

5.1. Опции настройки

Можно настроить опции Active Record Encryption, установив их в вашем application.rb (самый распространенный сценарий) или в файле определенной среды config/environments/<env name>.rb, если хотите установить их на основе среды.

Все опции настройки находятся в пространстве имен active_record.encryption.config. Например:

config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
config.active_record.encryption.store_key_references = true
config.active_record.encryption.extend_queries = true

Доступны следующие опции:

Ключ Значение
support_unencrypted_data Когда true, нешифрованные данные читаются, как обычно. Когда false, будет вызываться ошибка. По умолчанию: false.
extend_queries Когда true, запросы, ссылающиеся на детерминировано шифруемые атрибуты, будут модифицированы, чтобы по необходимости включать дополнительные значения. Эти дополнительные значения будут включать чистую версию значения (когда support_unencrypted_data true) и значения, зашифрованные с помощью предыдущих схем шифрования, если они имеются (предоставлены с помощью опции previous:). По умолчанию: false (экспериментально).
encrypt_fixtures Когда true, шифруемые атрибуты в фикстурах будут автоматически зашифрованы при загрузке. По умолчанию: false.
store_key_references Когда true, ссылка на ключ шифрования сохраняется в заголовках зашифрованного сообщения. Это сделано для более быстрого дешифрования при использовании нескольких ключей. По умолчанию: false.
add_to_filter_parameters Когда true, имена шифруемых атрибутов автоматически добавляются в список фильтруемых параметров, которые не показываются в логах. По умолчанию: true.
excluded_from_filter_parameters Можно настроить список параметров, которые не будут отфильтрованы, когда add_to_filter_parameters true. По умолчанию: [].
validate_column_size Добавляет валидацию, основанную на размере столбца. Это рекомендовано, чтобы избежать хранения огромных значений с использованием очень сжатой полезной нагрузки. По умолчанию: true.
master_key Ключ или список ключей, используемых для воспроизведения корневых ключей шифрования данных. Способ, которым они используются, зависит от настроенного провайдера ключа. Предпочтительнее настроить их с помощью учетных данных active_record_encryption.master_key.
deterministic_key Ключ или список ключей, используемых для детерминированного шифрования. Предпочтительнее настроить их с помощью учетных данных active_record_encryption.deterministic_key.
key_derivation_salt Соль, используемая при воспроизведении ключей. Предпочтительнее настроить ее с помощью учетных данных active_record_encryption.key_derivation_salt.

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

5.2. Контексты шифрования

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

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

Основные компоненты контекстов шифрования следующие:

  • encryptor: представляет внутренний API для шифрования и дешифрования данных. Он взаимодействует с key_provider для создания зашифрованных сообщений и имеет дело с их сериализацией. Само шифрование/дешифрование выполняется cipher, а сериализация message_serializer.
  • cipher сам алгоритм шифрования (Aes 256 GCM)
  • key_provider отдает ключи шифрования и дешифрования.
  • message_serializer: сериализует и десериализует зашифрованную нагрузку (Message).

Если решили создать свой message_serializer, важно использовать безопасные механизмы, которые не могут десериализовывать произвольные объекты. Обычный поддерживаемый сценарий, когда шифруются существующие незашифрованные данные. Злоумышленник может использовать это, вводя подделанную нагрузку до шифрования, и выполняя атаки RCE. Это означает, что пользовательские сериализаторы должны избегать Marshal, YAML.load (вместо него используйте YAML.safe_load) или JSON.load (вместо него используйте JSON.parse).

5.2.1. Глобальный контекст шифрования

Глобальный контекст шифрования это тот, который используется по умолчанию, и настраивается с помощью конфигурационных настроек в application.rb или конфигурационных файлах среды.

config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
config.active_record_encryption.encryptor = MyEncryptor.new
5.2.2. Контексты шифрования для атрибута

Можно переопределить параметр контекста шифрования, передав их в объявлении атрибута:

class Attribute
  encrypts :title, encryptor: MyAttributeEncryptor.new
end
5.2.3. Контекст шифрования при запуске блока кода

Можно использовать ActiveRecord::Encryption.with_encryption_context для настройки контекста шифрования для заданного блока кода:

ActiveRecord::Encryption.with_encryption_context(encryptor: ActiveRecord::Encryption::NullEncryptor.new) do
  ...
end
5.2.4. Встроенные контексты шифрования
5.2.4.1. Отключение шифрования

Можно запустить код без шифрования:

ActiveRecord::Encryption.without_encryption do
   ...
end

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

5.2.4.2. Защита зашифрованных данных

Можно запустить код без шифрования, но предотвращая перезапись зашифрованного содержимого:

ActiveRecord::Encryption.protecting_encrypted_data do
   ...
end

Это удобно, если вы хотите защитить зашифрованные данные, в то же время позволяя кому-то запускать произвольный код для них (например, в консоли Rails).