Rails: Manually decrypting a session cookie

Updated . Posted . Visible to the public.

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.

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

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

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.

# 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:

decrypt_session 'K2lUcDA1MjQ4b05RRU9zU2tNM05ldmIvdGpKVzNDdmRNNVkvbHFVSkNwT1lGODhkN3NZZHRYaDBwQXowR2lheUoxemt1Wm82Z0psYlFNVFM2dmxQaVNvRlhRZGNQQzNXUkswNnNTdVRPR1o5UURrY29CUjJvbEtXb2dwS2dTazZneG5XbjBzMVZISEVyc3ZkQzIxRW9FU3JERHZMWFg3Uk50Z2o0cVZ1eUF2VVR2RjdFbDUvaXlqUEorMEd6NGM0WjBhaTZOQ0NPaGE1NkZCTmVjMzdHajZueU56TVpQZk53bVJKZ21KWW9SdXFuc09WZVlMNS93aERSRlhLTWpEN3Y2M2xtSTlrUjNoS0lNQVMxNUhLNkpDekhhcUViZklLa0pSV3A2NzBtZmc9LS0yS1RmVTJyanl5dHpWQklkSlFQbVJRPT0%3D--50da898541a727755da8cffffbcfbb2c5dd3310b'
=> {
     "session_id"=>"8ef662867ab2457717ba74c143c08733",
     "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 Show archive.org snapshot and extended with the cookies serializer section. You will get an exception like ActiveSupport::MessageEncryptor::InvalidMessage when you use the wrong cookies serializer.

Last edit
Dominik Schöler
License
Source code in this card is licensed under the MIT License.
Posted by Emanuel to makandra dev (2019-10-28 09:54)