Fred Willmore
Fred Willmore

Reputation: 4614

ActiveSupport::MessageEncryptor different between rails 3.1 and 3.2

I have an old project that I'm attempting to move along the upgrade path from Rails 3.1 to 3.2. I've run into issues with ActiveSupport::MessageEncryptor - data encrypted in Rails 3.1 is not able to be decrypted in Rails 3.2 - I receive the following error: ActiveSupport::MessageEncryptor::InvalidMessage

Here is the output of the two sessions in the Rails console - first I encrypt a sample string in Rails 3.1.12:

Loading development environment (Rails 3.1.12)
2.1.5 :001 > secret = "01ac28236532ee53e0a0c4f562ce2f398c7e9287f5015d2baab0e1d986579a1a2fb6296946327ffffc3a"
 => "01ac28236532ee53e0a0c4f562ce2f398c7e9287f5015d2baab0e1d986579a1a2fb6296946327ffffc3a"
2.1.5 :002 > encryptor =  ActiveSupport::MessageEncryptor.new(secret)
 => #<ActiveSupport::MessageEncryptor:0x007fd8a6f607a8 @secret="01ac28236532ee53e0a0c4f562ce2f398c7e9287f5015d2baab0e1d986579a1a2fb6296946327ffffc3a", @cipher="aes-256-cbc">
2.1.5 :003 > encryptor.encrypt_and_sign("123-45-6789")
 => "BAhJIktNaE1kZHkwUHZZWVFub2RVeFk3MEY2Sm9LMjA3SzUzYXYxVStVb25HZlhjPS0tTzRXZmRwODJqZ1Boa0twdHlWeWthUT09BjoGRUY=--70b6be792b274777104f53c2f4f324320e9cd808"

now I attempt to decrypt the resulting string in the Rails 3.2 environment:

Loading development environment (Rails 3.2.21)
2.1.5 :001 > secret = "01ac28236532ee53e0a0c4f562ce2f398c7e9287f5015d2baab0e1d986579a1a2fb6296946327ffffc3a"
 => "01ac28236532ee53e0a0c4f562ce2f398c7e9287f5015d2baab0e1d986579a1a2fb6296946327ffffc3a"
2.1.5 :002 > encryptor =  ActiveSupport::MessageEncryptor.new(secret)
 => #<ActiveSupport::MessageEncryptor:0x007f8802a02508 @secret="01ac28236532ee53e0a0c4f562ce2f398c7e9287f5015d2baab0e1d986579a1a2fb6296946327ffffc3a", @cipher="aes-256-cbc", @verifier=#<ActiveSupport::MessageVerifier:0x007f8802a09bf0 @secret="01ac28236532ee53e0a0c4f562ce2f398c7e9287f5015d2baab0e1d986579a1a2fb6296946327ffffc3a", @digest="SHA1", @serializer=ActiveSupport::MessageEncryptor::NullSerializer>, @serializer=Marshal>
2.1.5 :003 > encryptor.decrypt_and_verify("BAhJIktNaE1kZHkwUHZZWVFub2RVeFk3MEY2Sm9LMjA3SzUzYXYxVStVb25HZlhjPS0tTzRXZmRwODJqZ1Boa0twdHlWeWthUT09BjoGRUY=--70b6be792b274777104f53c2f4f324320e9cd808")
ActiveSupport::MessageEncryptor::InvalidMessage: ActiveSupport::MessageEncryptor::InvalidMessage

In both cases I'm using the default cipher "aes-256-cbc"; I did notice that the ActiveSupport::MessageEncryptor object in rails 3.2 has a few more instance variables attached to it. I'd like to be able to rewrite my data in a new database field in a format that can be read under rails 3.2, so I'd like to get ActiveSupport::MessageEncryptor in 3.1 to have all the properties that it has in 3.2. Any idea what I'm missing?

Upvotes: 1

Views: 1075

Answers (1)

Fred Willmore
Fred Willmore

Reputation: 4614

I discovered my issue:

One of the properties that the MessageEncryptor object has under Rails 3.2 that it didn't in 3.1 is the MessageVerifier. (In 3.1, the function verifier wraps a call to MessageVerifier.new, whereas in 3.2 verifier is a reader for the instance variable @verifier, created on initialization.)

In 3.1 MessageVerifier uses Marshal for its serializer. In 3.2, it supports choosing another serializer. The initializer for MessageEncryptor calls MessageVerifier.new but specifies NullSerializer for the serializer.

So, while my old data had been encrypted by an encryptor whose verifier was using Marshal for its serializer, the new encryptor was attempting to read it using a verifier whose serializer is NullSerializer.

The most bewildering thing about all of this is that I couldn't find any posts about the same issue. How could this ever have worked for anyone?

Anyway, I solved my problem using a monkey patch on ActiveSupport::MessageEncryptor like so:

module ActiveSupport
  class MessageEncryptor
    def initialize(secret, options = {})

      unless options.is_a?(Hash)
        ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :cipher => 'algorithm' to specify the cipher algorithm."
        options = { :cipher => options }
      end

      @secret = secret
      @cipher = options[:cipher] || 'aes-256-cbc'

      # the default version of this function creates the verifier with NullSerializer, not providing the option to specify a serializer
      # however, in ActiveSupport 3.1 and earlier MessageVerifier uses the Marshal serializer; thus, all our existing data was verified
      # and subsequently encrypted using Marshal.
      # Considering migrating all existing data over to a new value using NullSerializer, as the latest version of ActiveSupport::MessageEncryptor
      # still has a verifier that's hard-coded to use NullSerializer. If we do that, then we can get rid of this patch once the migration is complete
      @verifier = MessageVerifier.new(@secret, :serializer => options[:verifier_serializer] || NullSerializer)
      @serializer = options[:serializer] || Marshal

    end
  end
end

Upvotes: 2

Related Questions