Railsのsecret_key_baseを理解する(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Railsのsecret_key_baseを理解する(翻訳)

secret_key_baseの値の意味や、Railsアプリでの使われ方を知りたいと思ったことはありませんか。この設定値はRails 4から導入され、developmentやproductionといった環境ごとに定義されるのが普通ですが、値の目的はシンプルです。すなわち、値はアプリのkey_generatorメソッドのsecret入力として使われます

このメソッドはRails.application.key_generatorでアクセスでき、引数を取らず、ActiveSupport::CachingKeyGeneratorインスタンスをひとつ返します。続いて、CachingKeyGeneratorクラスが提供するgenerate_keyメソッドを使ってキーを導出します。secret_key_baseは、さまざまなセキュリティ機能で個別のキーを引き続き使えるようにしつつ、開発者が(訳注: 個別のキーを)設定する負荷を軽減する役割を持っています。

CachingKeyGeneratorクラスは特にActiveSupport::KeyGeneratorクラスをラップします。このクラスは名前のとおり、導出したキーを内部のハッシュにキャッシュして保存します。ハッシュのエントリはsalt入力によってインデックス化されます(gist: rails_app_key_generator.rb)。

# rails_app_key_generator.rb
# アプリのkey_generatorからキーを求める
# 戻り値: #<ActiveSupport::CachingKeyGenerator:0x00000004ae1b00 @key_generator=#<ActiveSupport::KeyGenerator:0x00000004ae1b28 @secret="...", @iterations=1000>, @cache_keys=#<Concurrent::Map:0x00000004ae1ad8 entries=0 default_proc=nil>>
Rails.application.key_generator

# 戻り値: "\a\xDD|\xEB\xE2\xD3\xEC\x05\xCA@C\xFBVD\xFB\xE5\x93o\e(\xDA\x83\x95}\xD3\x15\x91V\xA5y&6"
Rails.application.key_generator.generate_key('salt input', 32)

アプリのkey_generatorsecret_key_baseは、Railsフレームワーク内の3つのコア機能で利用されます。

  1. 暗号化cookieで使うキーの導出: coookies.encryptedでアクセス可能
  2. HMAC署名されたcookieで使うキーの導出: cookies.encryptedでアクセス可能
  3. アプリのすべての名前付きmessage_verifierインスタンスで使うキーの導出

1. 暗号化cookie

このcookieは、暗号化によってコンテンツに完全性と機密性を提供します。Railsのセッションcookieは完全性と機密性のために暗号化cookieからビルドされます。

暗号化方式に応じて、secret_key_baseから1つまたは2つのキーが生成されます。

GCM暗号化方式が使われると、config.action_dispatch.authenticated_encrypted_cookie_saltで定義されたsaltを使ってキーが1つ生成されます。このデフォルト値は"authenticated encrypted cookie"です。

参考: Wikipedia-ja: Galois/Counter Mode (GCM)

CBC暗号化方式が使われると、キーが2つ導出されます。AESアルゴリズムをCBCモードで使っているので、メッセージはMACによる認証(authenticate)も必要です。暗号化キーとベリファイキーは、config.action_dispatch.encrypted_cookie_saltconfig.action_dispatch.encrypted_signed_cookie_saltで定義されたsaltを用いて導出されます。それぞれのデフォルト値は、"encrypted cookie""signed encrypted cookie"です。

参考: Wikipedia-ja: CBCモード (Cipher Block Chaining Mode)
参考: Wikipedia-ja: メッセージ認証コード(MAC: Message Authentication Code)

2. 署名済みcookie

署名済みcookieは、HMACとSHA1ハッシュ関数を用いたセキュリティによってコンテンツに完全性を提供します。署名済みcookieの実装は暗号化cookieの実装に似ており、secret_base_keyから導出したキーを1つ使います。

署名済みcookieで使うキーを導出するときは、config.action_dispatch.signed_cookie_saltで定義された設定値がsaltに使われます。デフォルト値は"signed cookie"です。

3. アプリのmessage_verifier

Railsフレームワークでsecret_key_baseが使われる最後の場所は、アプリのmessage_verifierメソッドであり、これもRails.applicationでアクセス可能です。アプリのkey_generatorメソッドの場合と同様に、このメソッドもverifier_nameのみを引数として取り、引数はインデックス化に使われてMessageVerifierインスタンスに保存されます。この引数は、secret_base_baseからキーを1つ導出する際のsalt入力としても用いられます(gist: rails_app_message_verifier.rb)。

# rails_app_message_verifier.rb 
# 戻り値:  #<ActiveSupport::MessageVerifier:0x00000003f4b430 @secret="...", @digest="SHA1", @serializer=Marshal>
Rails.application.message_verifier('bins')

# 戻り値: "BAh7BjoNdmVyaWZpZWRJIgp2YWx1ZQY6BkVU--9c516b0cc3bfcfd759550181541a24ca1294507e"
signed_msg = Rails.application.message_verifier('bins').generate({ verified: 'value' })

# 戻り値: {:verified=>"value"}
Rails.application.message_verifier('bins').verify(signed_msg)

# ActiveSupport::MessageVerifier::InvalidSignature をraiseする
Rails.application.message_verifier('bins').verify("unknown msg")

アプリのmessage_verifierメソッドは、メッセージ完全性機能のための簡単で便利なセキュリティAPIを提供し、いわゆる「remember me」トークンの実装や、署名済みURLによるリソースアクセス制御によく用いられます。このメソッドは、Rails 5.2で新しく導入されたActiveStorageの機能でも使われています。

ActiveSupport::KeyGeneratorについて

ActiveSupport::KeyGeneratorは、PBKDF2というKDF(鍵導出関数)の単なるラッパーです。このKDFのキー導出がパスワードベースであることを考えると、実際には最善のオプションではありません。特に、PBKDF2は人間が作ったパスフレーズを元に、キーのストレッチングと呼ばれるテクニックを反復的に使ってより強力なキーを生成する設計になっています。現実のRailsアプリで用いられるsecret_key_base値には、SecureRandomrake secretsでランダム生成されるセキュアな数値が使われるのが普通なので、人間が作ったパスフレーズよりも既に十分セキュアかつランダムな値になります。

実際には多くのRailsアプリで64バイト長のsecret_key_base値が使われているので、PBKDF2でキーを導出すると、キーから導出される実際の出力の長さは20バイト(160ビット)に限定されます。Rails全体にわたって導出されるキーには、HKDF(HMAC-based Key Derivation Function)を使う方がより適切のようです。HKDFでは「抽出の後拡大(extract-then-expand)」アプローチを採用しているので、より長いキーを生成できるようになります。

次回

私はRailsでのHKDF実装の検討を開始しました。この機能を改善できたらプルリクを作成するつもりです。今後にご期待ください。

関連記事

RailsのCSRF保護を詳しく調べてみた(翻訳)

Railsアプリの認証システムをセキュアにする4つの方法(翻訳)

Ruby: 8/27発表のRubyGems脆弱性と修正方法のまとめ

そのパッチをRailsに当てるべきかを考える(翻訳)

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ