Understanding the secret_key_base in Ruby on Rails

Have you ever wondered what the secret_key_base value is and how it’s used in a Rails application? This configuration value was introduced in Rails 4 and is usually defined on a per-environment basis. It’s purpose is simple: to be the secret input for the application’s key_generator method.

This method is accessible through Rails.application.key_generator. The method accepts no arguments and returns an ActiveSupport::CachingKeyGenerator instance. Keys are then derived using the generate_key method provided by the CachingKeyGenerator class. The secret_key_base is thus responsible for reducing the configuration burden on developers while still allowing separate and disperse security features to function using separate keys.

The CachingKeyGenerator in particular wraps the ActiveSupport::KeyGenerator class. As it’s name indicates, it caches and stores the derived key result in an internal Hash, where the entries are indexed by their salt input.

Deriving a key from the application’s key_generator

The application’s key_generator, and thus secret_key_base, are used by three core features within the Rails framework:

  1. Deriving keys for encrypted cookies which are accessible via coookies.encrypted.
  2. Deriving the key for HMAC signed cookies which are accessible via cookies.signed.
  3. Deriving keys for all of the application’s named message_verifier instances.

Encrypted Cookies

These cookies provide both integrity and confidentiality to their contents through encryption. Rails’ session cookies are built upon encrypted cookies because of these properties.

Depending on the cipher used one to two keys will be generated from the secret_key_base. If GCM encryption is used a key is derived using the salt defined by config.action_dispatch.authenticated_encrypted_cookie_salt. This value defaults to “authenticated encrypted cookie”.

If CBC encryption is used, two keys are derived. This is done because using AES in CBC mode we must also authenticate the message using a MAC. The encryption key and verification keys are derived using salts defined by the configuration values config.action_dispatch.encrypted_cookie_salt and config.action_dispatch.encrypted_signed_cookie_salt. They default to “encrypted cookie” and “signed encrypted cookie” respectively.

Signed Cookies

These cookies are secured using an HMAC with the SHA1 hash function. They thus provide integrity to their contents. They follow as a similar implementation as encrypted cookies and use a key derived from secret_base_key.

When deriving the key for signed cookies, the configuration value defined at config.action_dispatch.signed_cookie_salt is used for the salt. This value defaults to “signed cookie”.

Application Message Verifier

The last place secret_key_base is used in the Rails framework is by the application’s message_verifier method. Much like the application’s key_generator method, this method is also accessible via Rails.application. This method accepts a verifier_name string as it’s only argument. This argument is used to index and save the MessageVerifier instance. The argument is also used as the salt input for deriving a key from secret_base_base.

The application’s message_verifier method provides a easy and convenient security API for providing message integrity features. It is commonly used to implement “remember me” tokens or limiting access to resource with signed URL. This method is also used by the new ActiveStorage feature which was introduced in Rails 5.2.

About ActiveSupport::KeyGenerator

The ActiveSupport::KeyGenerator just wraps a Key Derivation Function named PBKDF2. This KDF is actually not the best option considering it is meant for password-based key derivation. Specifically, PBKDF2 is designed to take human-generated passphrases and, through a technique known as Key Stretching, produce a stronger key through an iterative process. The actual secret_key_base values used in real-world Rails applications are generated from secure random numbers usually using SecureRandom and rake secrets. As such, these values are already significantly more secure and sufficiently random than a human generated passphrase.

In fact, most Rails applications are using a secret_key_base value that is 64 bytes long. When using PBKDF2 for key derivation, the effective output for a given key is limited to 20-bytes or 160-bits. A better fit for the keys derived throughout Rails would be to use HKDF instead. HKDF employs an “extract-then-expand” approach and permits longer output keys to be generated as a result.

What’s Next

I’ve begun to explore implementing HKDF for Rails. I plan to create a Pull Request with these improvements in the future — stay tuned!