Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 48 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save wildjcrt/6359713fa770d277927051fdeb30ebbf to your computer and use it in GitHub Desktop.
Save wildjcrt/6359713fa770d277927051fdeb30ebbf to your computer and use it in GitHub Desktop.
Decrypt Rails 6.0 beta session cookies
require 'cgi'
require 'active_support'
def verify_and_decrypt_session_cookie(cookie, secret_key_base = Rails.application.secret_key_base)
config = Rails.application.config
cookie = CGI::unescape(cookie)
salt = config.action_dispatch.authenticated_encrypted_cookie_salt
encrypted_cookie_cipher = config.action_dispatch.encrypted_cookie_cipher || 'aes-256-gcm'
# serializer = ActiveSupport::MessageEncryptor::NullSerializer # use this line if you don't know your serializer
serializer = ActionDispatch::Cookies::JsonSerializer
key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
secret = key_generator.generate_key(salt, key_len)
encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: serializer)
session_key = config.session_options[:key].freeze
encryptor.decrypt_and_verify(cookie, purpose: "cookie.#{session_key}")
end
@lcmen
Copy link

lcmen commented May 11, 2023

If any of you is trying this in application upgraded from Rails 6 to Rails 7 and still and getting: 'final': OpenSSL::Cipher::CipherError error then you need to use pbkdf2_hmac_sha1. Here is an updated (and more flexible) version that should work on both: Rails 6 and Rails 7.

def decrypt_cookie(cookie)
  cookie = CGI.unescape(cookie)
  data, iv, auth_tag = cookie.split("--").map { |v| Base64.strict_decode64(v) }
  raise InvalidMessage if (auth_tag.nil? || auth_tag.bytes.length != 16)

  cipher = OpenSSL::Cipher.new("aes-256-gcm")
  secret = OpenSSL::PKCS5.pbkdf2_hmac(
    Rails.application.secret_key_base,
    Rails.configuration.action_dispatch.authenticated_encrypted_cookie_salt,
    1000,
    cipher.key_len,
    Rails.configuration.active_support.hash_digest_class.new
  )

  # Setup cipher for decryption and add inputs
  cipher.decrypt
  cipher.key = secret
  cipher.iv  = iv
  cipher.auth_tag = auth_tag
  cipher.auth_data = ""

  # Perform decryption
  cookie_payload = cipher.update(data)
  cookie_payload << cipher.final
  cookie_payload = JSON.parse(cookie_payload)

  message = ActiveSupport::Messages::Metadata.verify(cookie_payload, "decrypt")
  JSON.parse(Base64.decode64(cookie_payload["_rails"]["message"]))
end

CGI.unescape is used so cookie can be copied directly from a browser.

@dirkjonker
Copy link

If anyone has issues decrypting cookies outside of Rails in development after updating to Rails 7.1: this might be because the location of the secret_key_base was moved from tmp/development_secret.txt to tmp/local_secret.txt
so a simple cp tmp/development_secret.txt tmp/local_secret.txt might fix your issues

@bgvo
Copy link

bgvo commented Dec 21, 2023

In case anyone is interested, I put together a gem that makes it easy to incorporate session cookies decryption/encryption into any Rails' project: https://github.com/bgvo/rails_session_cipher

You can read about the motivation in my blog

@felipecsl
Copy link

I got this to work with Rails 7.1 by just removing the line message = ActiveSupport::Messages::Metadata.verify(cookie_payload, "decrypt") which wasn't working since ActiveSupport::Messages::Metadata.verify no longer exists

@felipecsl
Copy link

felipecsl commented Jan 25, 2024

Also wrote a port of this in Typescript for anyone interested https://gist.github.com/felipecsl/a6959e54caf2e53238306e2167e90ba2

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