Fix ActiveSupport::MessageEncryptor::InvalidMessage: ActiveSupport::MessageEncryptor::InvalidMessage in Rails
This error occurs when Rails cannot decrypt an encrypted message, usually because the secret_key_base has changed or the encrypted data is corrupted. This affects encrypted cookies, sessions, and credentials. Ensure your secret_key_base is consistent across deployments and environments, and re-encrypt any data if the key has been rotated.
Reading the Stack Trace
Here's what each line means:
- activesupport (7.1.3) lib/active_support/message_encryptor.rb:206:in `_decrypt': The decryption operation fails because the key does not match the one used to encrypt the data.
- activesupport (7.1.3) lib/active_support/messages/rotator.rb:22:in `decrypt_and_verify': The message rotator tries all configured keys but none can decrypt the message.
- app/controllers/application_controller.rb:12:in `current_user': The application tries to read the encrypted session to find the current user.
Common Causes
1. secret_key_base changed between deployments
The secret_key_base was regenerated or differs between environments, making existing encrypted data unreadable.
# config/secrets.yml or credentials
# Production used key 'abc123' to encrypt sessions
# After redeployment, key changed to 'xyz789'
# All existing user sessions are now invalid
2. Credentials file not shared across servers
In a multi-server setup, each server has a different master key, causing decryption failures.
# Server 1: RAILS_MASTER_KEY=key_one
# Server 2: RAILS_MASTER_KEY=key_two
# Encrypted cookies from server 1 cannot be read by server 2
3. Corrupted encrypted attribute
An ActiveRecord encrypted attribute stored in the database has been corrupted or manually modified.
class User < ApplicationRecord
encrypts :ssn
end
# Direct SQL update corrupted the encrypted value:
# UPDATE users SET ssn = 'not_encrypted_value' WHERE id = 1
The Fix
Ensure all servers use the same RAILS_MASTER_KEY environment variable or config/master.key file. For key rotation, configure previous keys so Rails can still decrypt data encrypted with old keys while encrypting new data with the current key.
# Different RAILS_MASTER_KEY on each server
# Server 1: RAILS_MASTER_KEY=abc123
# Server 2: RAILS_MASTER_KEY=xyz789
# Ensure consistent RAILS_MASTER_KEY across all servers
# Set via environment variable:
# export RAILS_MASTER_KEY=abc123def456ghi789
# Or use config/master.key file (git-ignored):
# echo 'abc123def456ghi789' > config/master.key
# For key rotation, configure old keys:
# config/initializers/encryption.rb
Rails.application.config.active_record.encryption.previous = [
{ primary_key: ENV['OLD_PRIMARY_KEY'] }
]
Testing the Fix
require 'rails_helper'
RSpec.describe 'Encryption' do
describe 'session handling' do
it 'handles invalid session gracefully' do
get '/dashboard', headers: {
'Cookie' => 'session=corrupted_data_here'
}
expect(response).to redirect_to(new_session_path)
end
end
describe User, type: :model do
it 'encrypts and decrypts ssn' do
user = User.create!(name: 'Test', ssn: '123-45-6789')
expect(user.reload.ssn).to eq('123-45-6789')
end
end
end
Run your tests:
bundle exec rspec spec/integration/encryption_spec.rb
Pushing Through CI/CD
git checkout -b fix/rails-encryption-key,git add config/initializers/encryption.rb,git commit -m "fix: configure encryption key rotation for consistent decryption",git push origin fix/rails-encryption-key
Your CI config should look something like this:
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
ports: ['5432:5432']
env:
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- run: bin/rails db:setup
- run: bundle exec rspec
The Full Manual Process: 18 Steps
Here's every step you just went through to fix this one bug:
- Notice the error alert or see it in your monitoring tool
- Open the error dashboard and read the stack trace
- Identify the file and line number from the stack trace
- Open your IDE and navigate to the file
- Read the surrounding code to understand context
- Reproduce the error locally
- Identify the root cause
- Write the fix
- Run the test suite locally
- Fix any failing tests
- Write new tests covering the edge case
- Run the full test suite again
- Create a new git branch
- Commit and push your changes
- Open a pull request
- Wait for code review
- Merge and deploy to production
- Monitor production to confirm the error is resolved
Total time: 30-60 minutes. For one bug.
Or Let bugstack Fix It in Under 2 minutes
Every step above? bugstack does it automatically.
Step 1: Install the SDK
gem install bugstack
Step 2: Initialize
require 'bugstack'
Bugstack.init(api_key: ENV['BUGSTACK_API_KEY'])
Step 3: There is no step 3.
bugstack handles everything from here:
- Captures the stack trace and request context
- Pulls the relevant source files from your GitHub repo
- Analyzes the error and understands the code context
- Generates a minimal, verified fix
- Runs your existing test suite
- Pushes through your CI/CD pipeline
- Deploys to production (or opens a PR for review)
Time from error to fix deployed: Under 2 minutes.
Human involvement: zero.
Try bugstack Free →No credit card. 5-minute setup. Cancel anytime.
Deploying the Fix (Manual Path)
- Ensure RAILS_MASTER_KEY is set consistently on all servers.
- Configure previous keys for rotation if the key has changed.
- Test encryption and decryption with the new configuration.
- Open a pull request.
- Merge and verify session handling across all servers.
Frequently Asked Questions
BugStack runs the fix through your existing test suite, generates additional edge-case tests, and validates that no other components are affected before marking it safe to deploy.
BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review it before merging.
Add the old key to the previous keys configuration, set the new primary key, and re-encrypt all encrypted attributes using Model.find_each(&:encrypt).
Never commit master.key or credentials encryption keys to version control. Use environment variables or a secrets manager like Vault for production deployments.