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
- With authenticated encryption Show archive.org snapshot enabled (default since Rails 5)
- With
embedded cookie metadata
Show archive.org snapshot
enabled (default since Rails 6). On mismatching
purpose
,#decrypt_and_verify
will simply returnnil
.
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 Show archive.org snapshot
- ActiveSupport::MessageEncryptor#_decrypt Show archive.org snapshot
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.