Rails 4: How to decrypt rails 4 session cookie (Given the session key and secret)
Rails 4 uses AES-256 to encrypt cookies with the key based on your app's secret_token_base
.
Here's the general scheme of decrypting a session cookie:
- calc your secret key
- Base 64 decode the cookie value
- split the decoded cookie value by '--', this will result in two parts, the first part is the encrypted data and the second is the initialization vector used by the encryption scheme. Base 64 decode each part independently.
- decrypt the encrypted data by applying AES decryption with the secret key and the initialization vector.
I couldn't find a website to easily decrypt the messages (advice is welcome), programmatically it can be done like this:
secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1(app_secret_token, 'encrypted cookie', 1000, 64)
encrypted_message = Base64.decode64(cookie_str)
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
cipher.decrypt
cipher.key = secret
cipher.iv = iv
decrypted_data = cipher.update(encrypted_data)
decrypted_data << cipher.final
Marshal.load(decrypted_data)
Couple of notes:
This code snippet is almost identical to the actual
_decript
method implementation inActiveSupport::MessageEncryptor
which is used by theActionDispatch::Cookies
middelware.This is all very much Rails 4 specific, from the ActionDispatch::Session::CookieJar:
If you only have secret_token set, your cookies will be signed, but not encrypted. This means a user cannot alter their +user_id+ without knowing your app's secret key, but can easily read their +user_id+. This was the default for Rails 3 apps.
If you have secret_key_base set, your cookies will be encrypted. This
goes a step further than signed cookies in that encrypted cookies cannot
be altered or read by users. This is the default starting in Rails 4.
How to decrypt a Rails 5 session cookie manually?
I have had the same problem the other day and figured out that the generated secret was 64 bytes long (on my mac), but Rails ensures that the key is 32 bytes long (source).
This has worked for me:
require 'cgi'
require 'json'
require 'active_support'
def verify_and_decrypt_session_cookie(cookie, secret_key_base)
cookie = CGI::unescape(cookie)
salt = 'encrypted cookie'
signed_salt = 'signed encrypted cookie'
key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
secret = key_generator.generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len]
sign_secret = key_generator.generate_key(signed_salt)
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
encryptor.decrypt_and_verify(cookie)
end
Or without ActiveSupport
:
require 'openssl'
require 'base64'
require 'cgi'
require 'json'
def verify_and_decrypt_session_cookie(cookie, secret_key_base)
cookie = CGI.unescape(cookie)
#################
# generate keys #
#################
encrypted_cookie_salt = 'encrypted cookie' # default: Rails.application.config.action_dispatch.encrypted_cookie_salt
encrypted_signed_cookie_salt = 'signed encrypted cookie' # default: Rails.application.config.action_dispatch.encrypted_signed_cookie_salt
iterations = 1000
key_size = 64
secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret_key_base, encrypted_cookie_salt, iterations, key_size)[0, OpenSSL::Cipher.new('aes-256-cbc').key_len]
sign_secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret_key_base, encrypted_signed_cookie_salt, iterations, key_size)
##########
# Verify #
##########
data, digest = cookie.split('--')
raise 'invalid message' unless digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, sign_secret, data)
# you better use secure compare instead of `==` to prevent time based attact,
# ref: ActiveSupport::SecurityUtils.secure_compare
###########
# Decrypt #
###########
encrypted_message = Base64.strict_decode64(data)
encrypted_data, iv = encrypted_message.split('--').map{|v| Base64.strict_decode64(v) }
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.decrypt
cipher.key = secret
cipher.iv = iv
decrypted_data = cipher.update(encrypted_data)
decrypted_data << cipher.final
JSON.load(decrypted_data)
end
Feel free to comment on the gist: https://gist.github.com/mbyczkowski/34fb691b4d7a100c32148705f244d028
encrypting session information in rails
Rails uses a secret token to sign the session. The raw data is still there, but changing it will cause it to not match the signature any more, and Rails will reject it. The cookie string looks like session_data--signature
, the session data is a base64-encoded marshalled object, and the signature is HMAC(session string, secret token)
.
The general assumption of the session data is that it is not secret (since it generally should contain only a few things, like a CSRF token and a user ID), but it should not be changeable by a user. The cookie signing accomplishes this.
If you need to actually encrypt the data so that users could never see it, you could do so using something like OpenSSL symmetric encryption, or you could switch to a non-cookie data store.
This is a variant on my own app's cookie store; I haven't tested it, but in theory this should generate actually-encrypted cookies for you. Note that this will be appreciably slower than the default cookie store, and depending on its runtime, could potentially be a DOS vector. Additionally, encrypted data will be lengthier than unencrypted data, and session cookies have a 4kb limit, so if you're storing a lot of data in your session, this might cause you to blow past that limit.
# Define our message encryptor
module ActiveSupport
class EncryptedMessageVerifier < MessageVerifier
def verify(message)
Marshal.load cryptor.decrypt_and_verify(message)
end
def generate(value)
cryptor.encrypt_and_sign Marshal.dump(value)
end
def cryptor
ActiveSupport::MessageEncryptor.new(@secret)
end
end
end
# And then patch it into SignedCookieJar
class ActionDispatch::Cookies::SignedCookieJar
def initialize(parent_jar, secret)
ensure_secret_secure(secret)
@parent_jar = parent_jar
@verifier = ActiveSupport::EncryptedMessageVerifier.new(secret)
end
end
How to encrypt the session cookie in rails 4
After reviewing the relevant Rails Documentation, This area provides the answer:
If you only have secret_token set, your cookies will be signed, but not encrypted. This means a user cannot alter their user_id without knowing your app's secret key, but can easily read their user_id. This was the default for Rails 3 apps.
If you have secret_key_base set, your cookies will be encrypted. This goes a step further than signed cookies in that encrypted cookies cannot be altered or read by users. This is the default starting in Rails 4.
secret_key_base
is located in rails 4 by default in: config/secrets.yml
. So actually: in rails 4 the default actually is to encrypt session cookies.
Storing an encrypted cookie with Rails
I'm re-posting JacobM's answer, that he deleted, because it was the correct answer and pointed me in the right direction. If he undeletes it, I'll delete this one and pick his as the best answer.
First of all, if you use
encrypt_and_verify
instead ofencrypt
it will
sign the cookie for you.However, when it comes to security, I always prefer to rely on
solutions that have been vetted in public, rather than rolling my own.
An example would be the encrypted-cookies gem.
Related Topics
Access Instance Variable from Outside the Class
Rbenv: Surviving Without Gemsets
How to Do Basic Authentication with Restclient
Class Method VS Constant in Ruby/Rails
Is Inject the Same Thing as Reduce in Ruby
How to Install Nokogiri Ruby Gem with Mkmf.Log Saying Libiconv Not Found
Get Path to Activestorage File on Disk
Establish a Connection to Another Database Only in a Block
Stack Level Too Deep Error in Ruby on Rails
One Liner in Ruby for Displaying a Prompt, Getting Input, and Assigning to a Variable
Why Pg::Uniqueviolation: Error: Duplicate Key Value Violates Unique Constraint
Are the Date, Time, and Datetime Classes Necessary
Full Url for an Image-Path in Rails 3
What Does the Fail Keyword Do in Ruby
How to Catch Errno::Econnreset Class in "Case When"