Splitwisdom

The Splitwise engineering blog.

Baking Encrypted Cookies with Rails

Joe Stein โ€ข November 14, 2025

So, you want to write an encrypted cookie in Rails?

Let's walk through how Rails turns a key and a value into an encrypted cookie!

cookies.encrypted[:] = ""

๐ŸŽ Wrap

Great! First, Rails packages that value up into a cookie payload. That's just the cookie value with some metadata for security.

Are you using cookies with metadata? (It's the default since Rails 6.0.)

Rails.application.config.action_dispatch.use_cookies_with_metadata =

OK, here's the payload:

When reading cookies, pur, which stands for "purpose", is cross-checked with the cookie's name. That way, an attacker can't copy the value of one cookie to one with a different name.

๐Ÿ“€ Serialize

But that payload is a Ruby hash. We need to turn that into a sequence of bytes to encrypt.
A string is a series of bytes, and JSON is a string, so let's use JSON, the default cookie serializer as of Rails 7.0:

Now we have a string. But to encrypt it, we still need an encryption key.

๐Ÿ”‘ Make an encryption key

We're going to use PBKDF2 to make one from your secret key base.
PBKDF2 will essentially hash your secret key base with a salt a lot (like 1,000 times).

So we also need a salt, your secret key base, and a hash function:

ENV["SECRET_KEY_BASE"] = ""

Rails.application.configure do |config|
  config.action_dispatch.authenticated_encrypted_cookie_salt = ""
  config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::
end

Lastly, the number of bytes we need our new key to have depends on the encryption cipher.
AES-GCM is a good cipher. How many bits long do you want your key to be?
(The default is 256, so I'd stick with that unless you have a reason not to.)

Rails.application.config.action_dispatch.encrypted_cookie_cipher = "aes--gcm"

Mix it all together, and... tada! ๐ŸŽ‰ Here's the secret key!
(It's bits; I Base64-encoded it for your human eyes.)

...

๐Ÿ” Encrypt!

Now that we have an encryption key, we're almost ready to encrypt the payload. All we need is a randomly-generated initialization vector , which helps us thwart decryption attacks and lets us decrypt the data on the other end:

...

And presto! Here's the encrypted payload. ๐Ÿ”’

...

AES-GCM also outputs an "authentication tag," which prevents tampering. You can think of it kind of like a built-in signature. Here's the authentication tag:

...

Now, we have:

  1. The ciphertext, which is the encrypted payload containing the cookie's value and some other stuff;
  2. The initialization vector, which is required for decryption;
  3. The authentication tag, which ensures that nobody's tampered with the ciphertext.

Finally, we concatenate these three together with -- as a separator, which allows us to
extract the pieces again for decryption. And voila! We have the cookie!

...--...--...
ยท ยท ยท

Whew! Rails cookies are complex. We learned about a whole host of configuration options that go into encrypting a single cookie:

ENV["SECRET_KEY_BASE"] = ""

Rails.application.configure do |config|
  config.action_dispatch.use_cookies_with_metadata =
  config.action_dispatch.authenticated_encrypted_cookie_salt = ""
  config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::
  config.action_dispatch.encrypted_cookie_cipher = "aes--gcm"
end