Given the session key and secret, how can we decrypt Rails cookies?
I've got a question about how Rails handles cookie encryption/decryption.
I've got this in my config/environment.rb
config.action_controller.session = {
:session_key => [some key],
:secret => [some secret]
}
And this in config/environment/production.rb et al.:
Action开发者_StackOverflow社区Controller::Base.session_options[:session_domain] = [some
domain]
So far, so good -- as long as all my Rails apps have the same session_key and secret, and are on the same domain, they can all use that same cookie.
However, a colleague now has a JSP application (on the same domain), with which he'd like to read the cookies I have set.
So, given a secret and an encrypted cookie value, how would we decrypt it to get the contents of that cookie?
(The docs seem to indicate this is one-way SHA1 encryption by default -- http://caboo.se/doc/classes/CGI/Session/CookieStore.html -- but then how would my Rails applications read the contents of a cookie that is one-way encrypted?)
Thanks in advance for any tips/pointers/insight,
Joe
If you pull the session.data field straight from the session data stored in your app's database (if you are using active_record_store in your environment.rb file)
config.action_controller.session_store = :active_record_store
... here is how you decode it and return the hash:
Marshal.load(ActiveSupport::Base64.decode64(@session.data))
... or in Rails >= 3.2 (thanks Chuck Vose)
Marshal.load(Base64.decode64(@session.data))
It is not encrypted at all.
Rails uses HMAC-SHA1 for encrypting cookie data, which is different from a one-way SHA1 encryption, as you suspected (see the Wikipedia article on HMAC for an explanation). The encryption is done by the ActiveSupport::MessageVerifier
class (source code is fairly readable). Here's an example based on a test Rails app:
secret = 'b6ff5a9c3c97bf89afe9a72e6667bafe855390e8570d46e16e9760f6394' +
'4ab05577b211ec2f43f6c970441518f0241775499bde055078f754c33b62f68ba27ca'
cookie = "_test_session=BAh7CCIYdXNlcl9jcmVkZW50aWFsc19pZGkGIhV1c2VyX2NyZW" +
"RlbnRpYWxzIgGAMzBlODkxZDQ2MWZhNjFkMDFmNzczMmJjNDdjMjIwZGFjMTY2NWEwNDMwZ" +
"DVjMmUxOWY5MDFjMjQ5NWQ4OTM1OGZlMzE3NzRiZTFiZjM1ZTVlZDY2ZGUzYTkwOWZjZTgw" +
"NTFlNGUxZWI0MTUzYTRjODZiMGZmMzM3NzliM2U3YzI6D3Nlc3Npb25faWQiJTgxNzk0Yjd" +
"kN2IxYzRjMDE0M2QwOTk5NTVjZjUwZTVm--25c8f3222ab1be9f2394e2795a9f2557b06d0a92"
session = cookie.split('=').last
verifier = ActiveSupport::MessageVerifier.new(secret, 'SHA1')
verifier.verify(session)
This should return the session hash you expect. To implement this in Java your colleague is going to have to duplicate the ActiveSupport::MessageVerifier#verify
method. Source code is in your gems directory (/usr/lib/ruby/gems/1.8/gems
on my system) at activesupport-2.3.5/lib/active_support/message_verifier.rb
.
Here's how to decrypt the session cookie in Rails 4
def decrypt_session_cookie(cookie)
cookie = CGI.unescape(cookie)
config = Rails.application.config
encrypted_cookie_salt = config.action_dispatch.encrypted_cookie_salt # "encrypted cookie" by default
encrypted_signed_cookie_salt = config.action_dispatch.encrypted_signed_cookie_salt # "signed encrypted cookie" by default
key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000)
secret = key_generator.generate_key(encrypted_cookie_salt)
sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
encryptor.decrypt_and_verify(cookie)
end
http://big-elephants.com/2014-01/handling-rails-4-sessions-with-go/
By default, Rails (before version 4) does not encrypt session cookies, it only signs them. To encrypt them, you need to do something like this:
ActionController::Base.session_store = EncryptedCookieStore
There are multiple plugins that provide that kind of encryption functionality.
So, if you're not specifically using an encrypted store, all the Java code needs to do is verify the cookie signature and decode the cookie. As Alex says in his answer, you would need to duplicate the functionality of ActiveSupport::MessageVerifier#verify
, and share the key with the Java application. That both verifies and decodes the cookie.
If you don't want to verify the signature (which I do NOT recommend), you can use Midwire's method of decoding from Base64 to view the session hash. In Ruby, this is:
Marshal.load(ActiveSupport::Base64.decode64(the_cookie_value))
I know this is old, but hope this helps somebody!
(Update: The question relates to Rails 3. Starting with Rails 4, session cookies are encrypted by default.)
I've written a Ruby gem to handle cookies managed by Rails apps. Reading its source you can understand how it works and possibly port it to Java so that your JSP app could use that:
https://github.com/rosenfeld/rails_compatible_cookies_utils
It's a single file with ~ 150 lines of code which also handles signed only cookie values and takes care of both signing/encrypting and verifying/decrypting, while you seem to only be concerned about decrypting. This is the method for decrypting:
https://github.com/rosenfeld/rails_compatible_cookies_utils/blob/master/lib/rails_compatible_cookies_utils.rb#L41-L52
It worths mentioning that besides the key and the secret you'll also need to know which serializer is used. It used to be Marshal but it seems the default for newly generated apps is now JSON. If Marshal was used then it may be tricky to convert that code to Java as you'd have to find a library which implements Ruby's Marshal#load.
精彩评论