-
-
Save wildjcrt/6359713fa770d277927051fdeb30ebbf to your computer and use it in GitHub Desktop.
Also, I had to remove cookie = CGI.unescape(cookie)
otherwise Base64.strict_decode64(v)
throws an ArgumentError: invalid base64
error. Also, I allow the salt to be configurable and check for invalid auth_tag
as warned by this comment.
So my method which works with Rails 7.0.4 is:
def verify_and_decrypt_cookie(key, secret_key_base = Rails.application.secret_key_base, purpose = nil)
raise "no cookie to decrypt" unless cookies[key]
data, iv, auth_tag = cookies[key].split("--").map { |v| ::Base64.strict_decode64(v) }
raise "auth_tag is invalid" if auth_tag.nil? || auth_tag.bytes.length != 16
purpose ||= "cookie.#{key}"
cipher = OpenSSL::Cipher.new("aes-256-gcm")
# Compute the encryption key
salt = Rails.configuration.action_dispatch.authenticated_encrypted_cookie_salt
secret = OpenSSL::PKCS5.pbkdf2_hmac(secret_key_base, salt, 1000, cipher.key_len, OpenSSL::Digest::SHA256.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 and verification
cookie_payload = cipher.update(data)
cookie_payload << cipher.final
message = ActiveSupport::Messages::Metadata.verify(cookie_payload, purpose)
raise "cannot verify cookie" if message.nil?
cookie_payload = JSON.parse(cookie_payload)
# Decode Base64 encoded stored data
decoded_stored_value = ::Base64.decode64(cookie_payload["_rails"]["message"])
JSON.parse(decoded_stored_value)
end
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.
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
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
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
Also wrote a port of this in Typescript for anyone interested https://gist.github.com/felipecsl/a6959e54caf2e53238306e2167e90ba2
In case anyone ever needs this, pre Rails 5.2 session cookies are decoded like this:
def self.decrypt_cookie(cookie, app_secret)
token_hashed = OpenSSL::PKCS5.pbkdf2_hmac_sha1(app_secret, "encrypted cookie", 1000, 32)
encrypted_message = Base64.decode64(cookie).split("--")[0]
decoded_cookie = Base64.strict_decode64(encrypted_message)
cipher = OpenSSL::Cipher.new("aes-256-cbc")
cipher.key = token_hashed
cipher.update(decoded_cookie)
end
Rails 5.2 introduced use_authenticated_cookie_encryption
, which changed the algorithm from aes-256-cbc
(old) to aes-256-gcm
(new). See here.
To verify the cookie, can use the following (from the Rails code):
Where
session_key
is the name given to the cookie. Ifmessage
is returned as nil, raise an error that the cookie is not verifiable (eg, is being spoofed).