Rails 4 not encrypting cookie contents
Your cookis is not encrypted, because you used the signed
method on the cookie jar, which, well, just signes the cookie content.
To encrypt the cookie, use the encrypted
method:
cookies.encrypted[:discount] = 45
# => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
cookies.encrypted[:discount] # => 45
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.
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.
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.
What is the difference between signed and encrypted cookies in Rails?
It's subtle, but the answer is in the documentation you provided. Signed cookies only guard against tampering, while encrypted cookies guard against reading and tampering.
More specifically, signed cookies call ActiveSupport::MessageVerifier
to append a digest (generated using secret_key_base
) to the cookie. If the value of the cookie is modified, the digest will no longer match, and without knowing the value of secret_key_base
, the cookie cannot be signed. The value of the cookie is merely base64 encoded, however, and can be read by anyone.
Encrypted cookies called ActiveSupport::MessageEncryptor
to actually encrypt the value of the cookie before generating the digest. Similar to signed cookies, if the value of cookie is modified the digest will no longer match, but additionally the value of the cookie cannot be decrypted without the secret_key_base
.
As to when you'd use encrypted versus signed cookies, it comes down to the sensitivity of the information you're storing in the cookie. If all you want to protect against is someone modifying the cookie, then sign it - but if you also need to keep the data secret, encrypt it.
Rails 4 Encrypted Cookie Replay Attack
After some research and some tinkering, I have come up with the following solution.
- When user logs in, create a random secret (random in the sense that subsequent secrets should have a low probability of matching)
- Store that secret in the session, i.e. in the cookie, as well as server side, I'm using the Dalli gem to provide memcached functionality
- On a request for a page that requires authentication, read the secret from the cookie, and make sure it exists server side
- On logout, delete secret from cache, so any subsequent requests using the same cookies will be invalidated
As long as the cookies cannot be tampered with, then this should be secure. Any thoughts/comments are welcome
Related Topics
Bootstrap Modal in Ruby on Rails Not Working
How to Serialize a Ruby Digest::Sha1 Instance Object
What Does "File.Sync = True" Do
How to Get the Nth Element of an Enumerable in Ruby
How to Call a JavaScript Function from an HTML.Erb
How to Check If a Resource Exists in an Aws S3Bucket
Ruby Sequel: Array Returned by Query Is Being Returned as a String Object, Not an Array Object
How to Create Automatically a Instance of Every Class in a Directory
Ruby on Rails - £ Sign Troubles
Shading Mask Algorithm for Radiation Calculations
Should Repeat a Number of Times
Ruby - Convert Integer to String
Expressing Conditional Haml Possibly with Ternary Operator