Rails の cookie の値はどうなっているんだろうかと思って覗いてみました。
環境
- Rails 4.2.5
- Ruby 2.0.0p481
コード
先人のコードをそのまま使いました。 bundle exec rails c
でコンソールを起動して下のコードを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
cookie = 'your cookie value' key = 'your secret key base' cookie = CGI::unescape(cookie) # Default values for Rails 4 apps key_iter_num = 1000 key_size = 64 ## Rails.application.config.action_dispatch.encrypted_cookie_salt salt = "encrypted cookie" ## Rails.application.config.action_dispatch.encrypted_signed_cookie_salt signed_salt = "signed encrypted cookie" key_generator = ActiveSupport::KeyGenerator.new( key, iterations: key_iter_num) secret = key_generator.generate_key(salt) sign_secret = key_generator.generate_key(signed_salt) encryptor = ActiveSupport::MessageEncryptor.new( secret, sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer) encryptor.decrypt_and_verify(cookie) |
cookie
にクッキーの値を代入し、 key
に secret_key_base
の値を代入すれば処理できます。
cookie の値
cookie の値は たとえば Chrome だったら Developer Tools で Resources を表示し、 左側に表示されるツリーから該当の Cookie を選択すれば値を取得できます(Version 47.0.2526.106 で確認)。
Cookie は name と value の組になりますが、 Rails 4 が作成する Cookie の name は 標準で _xxx_session
の形になります。 これは Rails.application.config.session_store
で指定されているもので、 標準では config/initializers/session_store.rb
で記述されています。
secret_key_base の値
標準では config/secrets.yml
に記載されています。
salt と secret
上のコードでは salt
と secret
に config
で設定されている値を渡しています。 標準では “encrypted cookie” と “signed encrypted cookie” になっています。
これらの値は コントローラの中で cookies
という変数チェックすると確認できます。
クッキーの値の例
Rails のテスト用アプリケーションでクッキーの値を取得してみました。 テスト用アプリケーションでは devise gem を使用しており、 クッキーを取得したとき ユーザID 2 のユーザでログインしていました。
|
{ "session_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "warden.user.user.key": [[2], "xxxxxxxxxxxxxxxxxxxxxxxxxx"], "_csrf_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } |
デコードの処理内容
クッキーデコード時の処理内容を確認します。
CGI.unescape
でクッキーに入っている値を URLデコード します。
キージェネレータを作成します。 キージェネレータはシークレットキーの値とイテレーション回数をインスタンス変数にもつオブジェクトで、初期化処理は次のようになっています。
|
# File activesupport/lib/active_support/key_generator.rb, line 10 def initialize(secret, options = {}) @secret = secret # The default iterations are higher than required for our key derivation uses # on the off chance someone uses this for password storage @iterations = options[:iterations] || 2**16 end |
デコード時にiterations
に 1000 を代入しているのは、Rails::Application
クラスで次のように1000が使用されているからです。
|
# Returns the application's KeyGenerator def key_generator # number of iterations selected based on consultation with the google security # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 @caching_key_generator ||= if secrets.secret_key_base unless secrets.secret_key_base.kind_of?(String) raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String, change this value in `config/secrets.yml`" end key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000) ActiveSupport::CachingKeyGenerator.new(key_generator) else ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token) end end |
salt
を元にキーを作成します。 メソッドgenerate_key
は下のように定義されています。
|
# File activesupport/lib/active_support/key_generator.rb, line 20 def generate_key(salt, key_size=64) OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) end |
OpenSSL::PKCS5.pbkdf2_hmac_sha1
はハッシュ関数 sha1 を用いて暗号化に使用するキーを生成するメソッドで、 ここでは 1000回 イテレーションを繰り返しています。
sign_secret
を上と同様にして生成します。
MessageEncryptor
のオブジェクトを生成します。 MessageEncryptor
のイニシャライザは下記のようになっています。
|
# File activesupport/lib/active_support/message_encryptor.rb, line 45 def initialize(secret, *signature_key_or_options) options = signature_key_or_options.extract_options! sign_secret = signature_key_or_options.first @secret = secret @sign_secret = sign_secret @cipher = options[:cipher] || 'aes-256-cbc' @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer) @serializer = options[:serializer] || Marshal end |
options[:chiper]
は設定されないまま実行されますから、 AES-256-CBC が暗号化方式として設定されます。 AES は共通鍵暗号で、 ここで作成したMessageEncryptor
オブジェクトはencrypt
とdecrypt
が可能です。
verify
を行って decrypt
します。 verify
を行うのは padding attack を防ぐためだそうです。 単純に decrypt するわけではないので、 たとえば secret の値が誤っていたりするとエラーが発生して復号後の文字列が取得できません。
cookie の値を作る
前節で書いたのと同じ要領で MessageEncryptor
のオブジェクトを作成して encrypt_and_sign
を実行すれば cookie の値を作成できます。 encrypt_and_sign
メソッドは実行する度に結果が変わります。
|
value = '{"session_id":"12345","key_1":"value_1","key_2":"value_2"}' cookie_value = encryptor.encrypt_and_sign(value) encryptor.decrypt_and_verify( encryptor.encrypt_and_sign(value)) == value # => true |
A Life Summary of an Gypsy