diff options
Diffstat (limited to 'activesupport/lib/active_support/message_encryptor.rb')
-rw-r--r-- | activesupport/lib/active_support/message_encryptor.rb | 73 |
1 files changed, 48 insertions, 25 deletions
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index e14386a85d..ada2e79ccb 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -1,5 +1,5 @@ require 'openssl' -require 'active_support/base64' +require 'base64' module ActiveSupport # MessageEncryptor is a simple way to encrypt values which get stored somewhere @@ -9,22 +9,56 @@ module ActiveSupport # # This can be used in situations similar to the <tt>MessageVerifier</tt>, but where you don't # want users to be able to determine the value of the payload. + # + # key = OpenSSL::Digest::SHA256.new('password').digest # => "\x89\xE0\x156\xAC..." + # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...> + # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." + # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" class MessageEncryptor + module NullSerializer #:nodoc: + def self.load(value) + value + end + + def self.dump(value) + value + end + end + class InvalidMessage < StandardError; end OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError + # Initialize a new MessageEncryptor. + # +secret+ must be at least as long as the cipher key size. For the default 'aes-256-cbc' cipher, + # this is 256 bits. If you are using a user-entered secret, you can generate a suitable key with + # <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or similar. + # + # Options: + # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc' + # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+. + # 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' + @verifier = MessageVerifier.new(@secret, :serializer => NullSerializer) @serializer = options[:serializer] || Marshal end - def encrypt(value) + # Encrypt and sign a message. We need to sign the message in order to avoid padding attacks. + # Reference: http://www.limited-entropy.com/padding-oracle-attacks + def encrypt_and_sign(value) + verifier.generate(_encrypt(value)) + end + + # Decrypt and verify a message. We need to verify the message in order to avoid padding attacks. + # Reference: http://www.limited-entropy.com/padding-oracle-attacks + def decrypt_and_verify(value) + _decrypt(verifier.verify(value)) + end + + private + + def _encrypt(value) cipher = new_cipher # Rely on OpenSSL for the initialization vector iv = cipher.random_iv @@ -36,12 +70,12 @@ module ActiveSupport encrypted_data = cipher.update(@serializer.dump(value)) encrypted_data << cipher.final - [encrypted_data, iv].map {|v| ActiveSupport::Base64.encode64s(v)}.join("--") + [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--") end - def decrypt(encrypted_message) + def _decrypt(encrypted_message) cipher = new_cipher - encrypted_data, iv = encrypted_message.split("--").map {|v| ActiveSupport::Base64.decode64(v)} + encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.decode64(v)} cipher.decrypt cipher.key = @secret @@ -55,23 +89,12 @@ module ActiveSupport raise InvalidMessage end - def encrypt_and_sign(value) - verifier.generate(encrypt(value)) + def new_cipher + OpenSSL::Cipher::Cipher.new(@cipher) end - def decrypt_and_verify(value) - decrypt(verifier.verify(value)) + def verifier + @verifier end - - - - private - def new_cipher - OpenSSL::Cipher::Cipher.new(@cipher) - end - - def verifier - MessageVerifier.new(@secret) - end end end |