Updated: Rails: Manually decrypting a session cookie

Posted . Visible to the public. Auto-destruct in 41 days
  • Added section for Rails 7

Changes

  • -This method helps you to **manually** decrypt the session cookie in Rails 5.2. Chrome can retrieve the session cookie string from `Dev Tools > Application > Cookies > _application_name_session`.
  • +Normally, Rails handles encryption and signing of cookies, and you don't have to deal with the matter. Should you need to decrypt a session cookie manually: here is how.
  • -By default Rails >= 5.2 app uses JSON as cookie serializer. Before Marshal was used to serialize cookies. You can find out your application's cookies serializer with `Rails.application.config.action_dispatch.cookies_serializer`.
  • +Obviously, you can only decrypt a session cookie from within the corresponding Rails application. Only the Rails application that encrypted a cookie has the secrets to decrypt it.
  • +
  • +## Rails 7
  • +- With [authenticated encryption](https://guides.rubyonrails.org/v7.0/configuring.html#config-action-dispatch-use-authenticated-cookie-encryption) enabled (default since Rails 5)
  • +- With [embedded cookie metadata](https://guides.rubyonrails.org/v7.0/configuring.html#config-action-dispatch-use-cookies-with-metadata) enabled (default since Rails 6). On mismatching `purpose`, `#decrypt_and_verify` will simply return `nil`.
  • +
  • +```ruby
  • +def decrypt_session_cookie(cookie_string)
  • + return if cookie.blank?
  • +
  • + cipher = ActiveSupport::MessageEncryptor.default_cipher
  • + key_length = ActiveSupport::MessageEncryptor.key_len(cipher)
  • + key_generator = Rails.application.key_generator
  • + salt = Rails.configuration.action_dispatch.authenticated_encrypted_cookie_salt
  • + secret = key_generator.generate_key(salt, key_length)
  • + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
  • +
  • + cookie = CGI::unescape(cookie_string.strip)
  • + session_name = Rails.application.config.session_options[:key]
  • +
  • + encryptor.decrypt_and_verify(cookie, purpose: "cookie.#{session_name}")
  • +rescue ActiveSupport::MessageEncryptor::InvalidMessage
  • + nil
  • +end
  • +```
  • +#### References
  • +- [ActionDispatch::Cookies::EncryptedKeyRotatingCookieJar](https://github.com/rails/rails/blob/v7.0.8.6/actionpack/lib/action_dispatch/middleware/cookies.rb#L645)
  • +- [ActiveSupport::MessageEncryptor#_decrypt](https://github.com/rails/rails/blob/v7.0.8.6/activesupport/lib/active_support/message_encryptor.rb#L186)
  • +
  • +## Rails 5
  • +By default Rails >= 5.2 app uses JSON as cookie serializer. Before, Marshal was used to serialize cookies. You can find out your application's cookies serializer with `Rails.application.config.action_dispatch.cookies_serializer`.
  • ```rb
  • # Available modes: json, marshal
  • def decrypt_session(cookie_string, mode = 'json')
  • serializer = case mode
  • when 'json' then JSON
  • when 'marshal' then ActiveSupport::MessageEncryptor::NullSerializer
  • end
  • cookie = CGI::unescape(cookie_string.strip)
  • salt = Rails.configuration.action_dispatch.encrypted_cookie_salt
  • signed_salt = Rails.configuration.action_dispatch.encrypted_signed_cookie_salt
  • key_generator = ActiveSupport::KeyGenerator.new(Rails.application.secret_key_base, iterations: 1000)
  • secret = key_generator.generate_key(salt)[0, 32]
  • sign_secret = key_generator.generate_key(signed_salt)
  • encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: serializer)
  • result = encryptor.decrypt_and_verify(cookie)
  • (mode == 'marshal') ? Marshal.load(result) : result
  • end
  • ```
  • Example session cookie:
  • ```rb
  • decrypt_session 'K2lUcDA1MjQ4b05RRU9zU2tNM05ldmIvdGpKVzNDdmRNNVkvbHFVSkNwT1lGODhkN3NZZHRYaDBwQXowR2lheUoxemt1Wm82Z0psYlFNVFM2dmxQaVNvRlhRZGNQQzNXUkswNnNTdVRPR1o5UURrY29CUjJvbEtXb2dwS2dTazZneG5XbjBzMVZISEVyc3ZkQzIxRW9FU3JERHZMWFg3Uk50Z2o0cVZ1eUF2VVR2RjdFbDUvaXlqUEorMEd6NGM0WjBhaTZOQ0NPaGE1NkZCTmVjMzdHajZueU56TVpQZk53bVJKZ21KWW9SdXFuc09WZVlMNS93aERSRlhLTWpEN3Y2M2xtSTlrUjNoS0lNQVMxNUhLNkpDekhhcUViZklLa0pSV3A2NzBtZmc9LS0yS1RmVTJyanl5dHpWQklkSlFQbVJRPT0%3D--50da898541a727755da8cffffbcfbb2c5dd3310b'
  • => {
  • "session_id"=>"8ef662867ab2457717ba74c143c08733",
  • - "timestamp"=>1572261371, "warden.user.user.key"=>[[3],
  • - "$2a$13$FlVrgrbRbFRaFun/4dhaK."],
  • + "timestamp"=>1572261371,
  • + "warden.user.user.key"=>[[3], "$2a$13$FlVrgrbRbFRaFun/4dhaK."],
  • "_csrf_token"=>"e03pX09Pqfj3syQp0w9AAJ3fEh7I9Sm8VhndHfqQxgw="
  • }
  • ```
  • This method is based on [Decrypt a Rails 5 session cookie](https://gist.github.com/erose/36a514bc5ac9c5f18552369265b4d449) and extended with the cookies serializer section. You will get an exception like `ActiveSupport::MessageEncryptor::InvalidMessage` when you use the wrong cookies serializer.
Dominik Schöler
License
Source code in this card is licensed under the MIT License.
Posted by Dominik Schöler to makandra dev (2024-11-22 13:39)