LoginSignup
3
1

More than 3 years have passed since last update.

Rails 5.2/6.0 で MessageEncryptor を使うときは初期化タイミングに気を付けよう

Last updated at Posted at 2020-06-03

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 であることがわかる。

実例

実際のアプリケーションでは、なんらかのモデルに持たせておいて利用することもあるだろう。

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

これも同じように確認してみる。

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

問題なさそうに見える。

問題例

ところが、 MessageEncryptor の初期化タイミングが早すぎると、異なる暗号方式になってしまう。例えば after_initialize コールバック内で SomeModel 定数が参照されていると、そのタイミングでオートロードされ、 MessageEncryptor が生成されることとなる。

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"

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 となってしまう。

該当コードはこのあたりである。

解決例

初期化が早すぎるのが問題なので、実際の利用タイミングまで遅らせることが考えられる。

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

これでほとんどの場合 aes-256-gcm が利用できる。

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

ただし、 MessageEncryptor を利用したいタイミング自体が use_authenticated_message_encryption の設定反映前である場合は、これでも解決とはならない。そのときは MessageEncryptor.newcipher: オプションを明示するのが良いだろう。

Rails 6

以下のバージョンで改めて調査したが、同じ状況になるようだ。

  • Ruby 2.7.1p83
  • Rails 6.0.3.1

5.2 と少し事情が違うのは、デフォルトのオートローダーが Zeitwerk となり config/initializers/*.rb で各モデルをオートロードするのが非推奨となった点だ。このため config/initializers/*.rb でうっかりモデルを参照してしまった場合は、警告メッセージにて検出が可能となっている。しかしながら本記事での例のように、 config.after_initialize では警告はないようだ。

Rails では各ファイルは必要に応じてロードされるが、そのタイミングはときに分かりづらいこともある。ロードタイミングによって問題が発生することもあるので気を付けよう。

3
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1