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:
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`.
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.
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
.
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
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 the
cipher:option in
MessageEncryptor.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