Given the Session Key and Secret, How to Decrypt Rails Cookies

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:

  1. calc your secret key
  2. Base 64 decode the cookie value
  3. 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.
  4. 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 in ActiveSupport::MessageEncryptor which is used by the ActionDispatch::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 of encrypt 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



Leave a reply



Submit