Rails 5.2 から ActiveSupport::MessageEncryptor
のデフォルト暗号方式が aes-256-cbc
から aes-256-gcm
に変更となった。しかし、初期化タイミングによっては意図しない暗号方式を使うことになる場合があるため紹介する。
Rails 5.2
以下のバージョンで確認:
- Ruby 2.6.5
- Rails 5.2.4.3
デフォルト
まず 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"
確かに aes-256-gcm
であることがわかる。
実例
実際のアプリケーションでは、なんらかのモデルに持たせておいて利用することもあるだろう。
class SomeModel < ApplicationRecord
class << self
attr_reader :encryptor
def set_key(key)
@encryptor = ActiveSupport::MessageEncryptor.new key
end
end
set_key 'x' * 32
end
これも同じように確認してみる。
irb> SomeModel.encryptor.instance_variable_get '@cipher'
=> "aes-256-gcm"
問題なさそうに見える。
問題例
ところが、 MessageEncryptor
の初期化タイミングが早すぎると、異なる暗号方式になってしまう。例えば after_initialize
コールバック内で SomeModel
定数が参照されていると、そのタイミングでオートロードされ、 MessageEncryptor
が生成されることとなる。
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"
aes-256-gcm
ではなく aes-256-cbc
となってしまった。
原因
ことの問題は Rails.application.config.active_support.use_authenticated_message_encryption
の設定反映のタイミングにある。この設定値は過去バージョンとの互換性のために設けられており、 false
に設定することでデフォルト暗号方式を aes-256-cbc
に戻すことができる。本設定値は Rails 5.2 としてはデフォルト true
ではあるが、この設定値が config.after_initialize
実行時点ではまだ反映されていないようなのだ。従ってこのタイミングで初期化してしまうと、 MessageEncryptor
自身のデフォルトである aes-256-cbc
となってしまう。
該当コードはこのあたりである。
解決例
初期化が早すぎるのが問題なので、実際の利用タイミングまで遅らせることが考えられる。
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.after_initialize do
SomeModel2
end
これでほとんどの場合 aes-256-gcm
が利用できる。
irb> SomeModel2.encryptor.instance_variable_get '@cipher'
=> "aes-256-gcm"
ただし、 MessageEncryptor
を利用したいタイミング自体が use_authenticated_message_encryption
の設定反映前である場合は、これでも解決とはならない。そのときは MessageEncryptor.new
に cipher:
オプションを明示するのが良いだろう。
Rails 6
以下のバージョンで改めて調査したが、同じ状況になるようだ。
- Ruby 2.7.1p83
- Rails 6.0.3.1
5.2 と少し事情が違うのは、デフォルトのオートローダーが Zeitwerk となり config/initializers/*.rb
で各モデルをオートロードするのが非推奨となった点だ。このため config/initializers/*.rb
でうっかりモデルを参照してしまった場合は、警告メッセージにて検出が可能となっている。しかしながら本記事での例のように、 config.after_initialize
では警告はないようだ。
Rails では各ファイルは必要に応じてロードされるが、そのタイミングはときに分かりづらいこともある。ロードタイミングによって問題が発生することもあるので気を付けよう。