Skip to content

Instantly share code, notes, and snippets.

@mbyczkowski
Forked from tonytonyjan/rails_42_with_active_support.rb
Last active September 28, 2023 03:56
Show Gist options
  • Save mbyczkowski/34fb691b4d7a100c32148705f244d028 to your computer and use it in GitHub Desktop.
Save mbyczkowski/34fb691b4d7a100c32148705f244d028 to your computer and use it in GitHub Desktop.
session cookie decrypter for Rails 4.2+
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
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::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
@erose
Copy link

erose commented Mar 12, 2017

Lovely, thank you! 🎉

In line 10 of the first one, you use ActiveSupport::MessageEncryptor.key_len which does not exist in Rails 5.0.0. I substituted OpenSSL::Cipher.new('aes-256-cbc').key_len, which was 32 on my machine.

@mbyczkowski
Copy link
Author

@tadast
Copy link

tadast commented Mar 15, 2018

For Rails 5.1 I needed to remove serializer: JSON https://gist.github.com/tadast/769541b7fb82b31466dc620af40fe362

@9mm
Copy link

9mm commented Jul 1, 2018

How did you guys handle development where Rails.application.secrets.secret_key_base is nil?

@nevans
Copy link

nevans commented Mar 31, 2020

For what it's worth, this didn't work for me under rails 5.2.4, but here's what worked for me: https://gist.github.com/nevans/558b69f227c243f63552a6f91915424f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment