Table of Contents
I wondered how the Rails cookie structure is and checked it.
Environment
- Rails 4.2.5
- Ruby 2.0.0p481
Code to Decode Cookie Value
First, I checked the cookie value. Launch rails console, bundle exec rails c, and execute the following code.
|
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) |
Set cookie value to the variable cookie, and set secret_key_base value to the variable key.
cookie value
If you use Chrome browser (Version 47.0.2526.106), you can see the cookie value with developer tools. Open Developer Tools and choose resource view and select cookie that you want to see on left side tree.
Cookie is name and value pair. The name of the cookie generated by Rails 4 is like _xxx_session as default. You can specify it as Rails.application.config.session_store, which is written in config/initializers/session_store.rb usually.
secret_key_base value
Normally, it is written in config/secrets.yml.
salt and secret
In the above code, I set values defined as config to salt and secret. It is “encrypted cookie” and “signed encrypted cookie” as default.
You can see that values by checking cookies variable in controller.
Example of Cookie Value
Rails のテスト用アプリケーションでクッキーの値を取得してみました。 テスト用アプリケーションでは devise gem を使用しており、 クッキーを取得したとき ユーザID 2 のユーザでログインしていました。
|
1 2 3 4 5 |
{ "session_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "warden.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::Applicationclass で次のように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)endendGenerate key from
salt.generate_keymethod is defined as below.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_sha1generates key for encryption by hash method sha1. In this case, iterates 1000 times.Generate
sign_secretlike above.Crete
MessageEncryptorobject. Initializer ofMessageEncryptoris defined as below.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が可能です。verifyanddecrypt.verifyを行うのは padding attack を防ぐためだそうです。 It doesn’t decrypt simply, so incorrect secret value trigger exception and you can’t get decrypted value.
Generate Cookie Value
前節で書いたのと同じ要領で 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 |
