読者です 読者をやめる 読者になる 読者になる

ActiveSupport::MessageEncryptorを慎重に使う

経緯

Rubyを2.3系から2.4.1に上げたら、ActiveSupport::MessageEncryptorを使っているところが壊れた。ちなみにRailsは5.0.1。

key must be 32 bytes
/Users/nisshiee/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/activesupport-5.0.1/lib/active_support/message_encryptor.rb:79:in `key='
/Users/nisshiee/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/activesupport-5.0.1/lib/active_support/message_encryptor.rb:79:in `_encrypt'
/Users/nisshiee/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/activesupport-5.0.1/lib/active_support/message_encryptor.rb:60:in `encrypt_and_sign'
...

起こっていたこと

RailsではなくRubyの方のopensslパッケージが変更されていた。

github.com

これまではkeyに長過ぎる値を渡した場合は勝手に正しい長さに切り取ってくれたが、「想定外の結果(unexpected encryption/decryption results)」を引き起こすとして、正しい長さのkeyを渡さないと例外がraiseされるようになった。

これを受けて、Rails側も、いくつかのデフォルト引数の値が見直された。この変更は v5.1.0.rc1 には入っているので、5.1.0のときには適用されていると思われる。

github.com

ただし、この変更は「長過ぎるkeyのトリミングは代わりにRailsが担いますよ」というものではないので、例えば以下のようなコードを書いている場合は引き続き例外がraiseされると思われる(未検証)。

ActiveSupport::MessageEncryptor.new("<長過ぎるkey>")

どう書くべきか

結論から書くと、こんな感じが良いと思う。

ENCRYPT_CIPHER = 'aes-256-cbc'
ENCRYPTOR = begin
  key_len = ActiveSupport::MessageEncryptor
            .key_len(ENCRYPT_CIPHER)
  key = ActiveSupport::KeyGenerator
        .new("<環境から挿入したkey>")
        .generate_key("<環境から挿入したsalt>", key_len)
  ActiveSupport::MessageEncryptor.new(
    key,
    cipher:     ENCRYPT_CIPHER,
    digest:     'SHA1',
    serializer: Marshal,
  )
end

方針としては、

  • デフォルト値には頼らない(updateで値を変えられちゃたまらん)
  • keyはActiveSupport::KeyGeneratorで生成する

という感じ。とにかく暗号化して保存しておいたものが、突然復号化できなくなるという悪夢は避けたい。

ちなみに、動かすだけなら

ActiveSupport::MessageEncryptor.new("<ピッタリ32byteになる文字列>")

でも動くのだが、

  • デフォルト引数に頼っているのでデフォルト値の変更には弱い
  • keyを文字列で記載しているので、keyの32byte分の情報量を使い切れておらず、ちょっと暗号が弱くなってる(・・・かも。専門家じゃないのでただの予想)

と思われるので、ActiveSupport::KeyGeneratorを使って、長めのkey文字列から32byteのkeyを生成したほうが良さそう。

※ 本記事の内容は予測も含まれるので、指摘大歓迎