Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

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

概要

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

参考: §3.2.55 config.secret_key_base -- Rails アプリケーションの設定項目 - Railsガイド


  • 2017/10/24: 初版公開
  • 2023/12/01: 更新

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)も必要です(リンク切れ)

暗号化キーとベリファイキーは、定義されたsaltを用いて導出されます。

  • config.action_dispatch.encrypted_cookie_salt(デフォルト値: "encrypted cookie"
  • config.action_dispatch.encrypted_signed_cookie_salt(デフォルト値: "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で新しく導入されたActive Storageの機能でも使われています。

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に当てるべきかを考える(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。