目次
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 のユーザでログインしていました。
1 2 3 4 5 |
{ "session_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "warden.user.user.key": [[2], "xxxxxxxxxxxxxxxxxxxxxxxxxx"], "_csrf_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } |
デコードの処理内容
クッキーデコード時の処理内容を確認します。
CGI.unescape
でクッキーに入っている値を URLデコード します。キージェネレータを作成します。 キージェネレータはシークレットキーの値とイテレーション回数をインスタンス変数にもつオブジェクトで、初期化処理は次のようになっています。
1234567# File activesupport/lib/active_support/key_generator.rb, line 10def 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**16endデコード時に
iterations
に 1000 を代入しているのは、Rails::Application
クラスで次のように1000が使用されているからです。123456789101112131415# Returns the application's KeyGeneratordef 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_baseunless 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`"endkey_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000)ActiveSupport::CachingKeyGenerator.new(key_generator)elseActiveSupport::LegacyKeyGenerator.new(secrets.secret_token)endendsalt
を元にキーを作成します。 メソッドgenerate_key
は下のように定義されています。1234# File activesupport/lib/active_support/key_generator.rb, line 20def generate_key(salt, key_size=64)OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)endOpenSSL::PKCS5.pbkdf2_hmac_sha1
はハッシュ関数 sha1 を用いて暗号化に使用するキーを生成するメソッドで、 ここでは 1000回 イテレーションを繰り返しています。sign_secret
を上と同様にして生成します。MessageEncryptor
のオブジェクトを生成します。MessageEncryptor
のイニシャライザは下記のようになっています。12345678910# File activesupport/lib/active_support/message_encryptor.rb, line 45def 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] || Marshalendoptions[:chiper]
は設定されないまま実行されますから、 AES-256-CBC が暗号化方式として設定されます。 AES は共通鍵暗号で、 ここで作成したMessageEncryptor
オブジェクトはencrypt
とdecrypt
が可能です。verify
を行ってdecrypt
します。verify
を行うのは padding attack を防ぐためだそうです。 単純に decrypt するわけではないので、 たとえば secret の値が誤っていたりするとエラーが発生して復号後の文字列が取得できません。
cookie の値を作る
前節で書いたのと同じ要領で MessageEncryptor
のオブジェクトを作成して encrypt_and_sign
を実行すれば cookie の値を作成できます。 encrypt_and_sign
メソッドは実行する度に結果が変わります。
1 2 3 4 5 6 7 |
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 |