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.
The application’s 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
cookies.encrypted
. - Deriving the key for HMAC signed cookies which are accessible via
cookies.signed
. - 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!