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
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.
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.
key_generator, and thus
secret_key_base, are used by three core features within the Rails framework:
- Deriving keys for encrypted cookies which are accessible via
- Deriving the key for HMAC signed cookies which are accessible via
- Deriving keys for all of the application’s named
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_signed_cookie_salt. They default to
“encrypted cookie” and
“signed encrypted cookie” respectively.
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
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
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
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.
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
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.
I’ve begun to explore implementing HKDF for Rails. I plan to create a Pull Request with these improvements in the future — stay tuned!