Be careful of initialization timing when using MessageEncryptor with Rails 5.2 / 6.0

Starting with Rails 5.2, the default encryption method for ʻActiveSupport :: MessageEncryptor has changed from ʻaes-256-cbc to ʻaes-256-gcm`. However, depending on the initialization timing, an unintended encryption method may be used, so we will introduce it.

Rails 5.2

Check with the following versions:

Default

First, check the default encryption method with rails console.

irb> encryptor = ActiveSupport::MessageEncryptor.new 'key'
=> #<ActiveSupport::MessageEncryptor:0x0000561c2eae8940 @secret="key", @sign_secret=nil, @cipher="aes-256-gcm", @aead_mode=true, @verifier=ActiveSupport::MessageEncryptor::NullVerifier, @serializer=Marshal, @options={}, @rotations=[]>
irb> encryptor.instance_variable_get '@cipher'
=> "aes-256-gcm"

It turns out that it is ʻaes-256-gcm`.

Illustration

In an actual application, it may be used by having it in some model.

app/models/some_model.rb


class SomeModel < ApplicationRecord
  class << self
    attr_reader :encryptor

    def set_key(key)
      @encryptor = ActiveSupport::MessageEncryptor.new key
    end
  end

  set_key 'x' * 32
end

Check this in the same way.

irb> SomeModel.encryptor.instance_variable_get '@cipher'
=> "aes-256-gcm"

Looks fine.

Problem example

However, if the initialization timing of MessageEncryptor is too early, the encryption method will be different. For example, if the SomeModel constant is referenced in the ʻafter_initialize callback, it will be autoloaded at that timing and MessageEncryptor` will be generated.

config/application.rb


class Application < Rails::Application
  config.load_defaults 5.2
  config.after_initialize do
    SomeModel
  end
end
irb> SomeModel.encryptor.instance_variable_get '@cipher'
=> "aes-256-cbc"

It has become ʻaes-256-cbc instead of ʻaes-256-gcm.

Cause

The problem is the timing of reflecting the settings of Rails.application.config.active_support.use_authenticated_message_encryption. This setting is provided for compatibility with previous versions, and you can set it to false to return the default cipher to ʻaes-256-cbc. This setting value is the default true for Rails 5.2, but it seems that this setting value has not been reflected yet at the time of executing config.after_initialize. Therefore, if it is initialized at this timing, it will become the default of MessageEncryptor itself, ʻaes-256-cbc.

The corresponding code is around here.

https://github.com/rails/rails/blob/v5.2.4.3/activesupport/lib/active_support/message_encryptor.rb#L88

Solution example

Since the problem is that the initialization is too early, it is possible to delay it until the actual usage timing.

app/models/some_model2.rb


class SomeModel2 < ApplicationRecord
  class << self
    def set_key(key)
      @key = key
    end

    def encryptor
      @encryptor ||= ActiveSupport::MessageEncryptor.new @key
    end
  end

  set_key 'x' * 32
end

config/application.rb


config.after_initialize do
  SomeModel2
end

Now you can use ʻaes-256-gcm` in most cases.

irb> SomeModel2.encryptor.instance_variable_get '@cipher'
=> "aes-256-gcm"

However, if the timing itself when you want to use MessageEncryptor is before the setting of ʻuse_authenticated_message_encryptionis reflected, this does not solve the problem. In that case, it is better to specify thecipher:option inMessageEncryptor.new`.

Rails 6

I investigated again with the following version, but it seems that the situation is the same.

A little different from 5.2 is that the default autoloader is Zeitwerk and it is deprecated to autoload each model with config / initializers / *. Rb. Therefore, if you inadvertently refer to the model with config / initializers / *. Rb, you can detect it with a warning message. However, as in the example in this article, there seems to be no warning in config.after_initialize.

Rails loads each file as needed, but the timing can sometimes be confusing. Be aware that load timing can cause problems.

Recommended Posts

Be careful of initialization timing when using MessageEncryptor with Rails 5.2 / 6.0
Be careful when using rails_semantic_logger with unicorn
[Rails] When using ajax, be aware of "CSRF measures".
Be careful when using multiple articles
Initialization timing of member variable with final specified
Be careful with requests and responses when using the Serverless Framework in Java
Things to be aware of when using devise's lockable
[Java10] Be careful of using var and generics together
How to set environment variables when using Payjp with Rails
Try using view_component with rails
Japaneseize using i18n with Rails
Summary of initial work when creating an app with Rails
Error when using rails capybara
[Rails] Where to be careful in the description of validation
Detailed tips when using Rails
[Rails] Japanese localization of error message when using Form object
Things to keep in mind when using Sidekiq with Rails
Be careful when upgrading Tomcat on a Web system using Oracle
[Rails] Implementation of coupon function (with automatic deletion function using batch processing)
Settings that should be done when operating a production environment with Rails
[Rails] "pry-rails" that can be used when saving with the create method
A collection of methods often used when manipulating time with TimeWithZone of Rails
Visualize Rails server processing time using Server Timing
[Rails] Posts cannot be deleted when commented! ??
Notes on using FCM with Ruby on Rails
Using hidden type when PUT with Thymeleaf
Using SSIServlet with built-in Tomcat of SpringBoot 2.2.4
Using Material Design for Bootstrap with Rails 5.2
Be careful of daylight saving time when newly registering / referencing a user's birthday
Problem of slow processing when using Active Storage with cloud storage (GCS, S3, etc)
Be absolutely careful when putting the result of and / or in a variable!