123456789_123456789_123456789_123456789_123456789_

Class: ActiveRecord::Encryption::Encryptor

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Subclasses:
Inherits: Object
Defined in: activerecord/lib/active_record/encryption/encryptor.rb

Overview

An encryptor exposes the encryption API that EncryptedAttributeType uses for encrypting and decrypting attribute values.

It interacts with a KeyProvider for getting the keys, and delegate to Cipher the actual encryption algorithm.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(compress: true, compressor: nil) ⇒ Encryptor

Options

  • :compress - Boolean indicating whether records should be compressed before encryption. Defaults to true.

  • :compressor - The compressor to use.

    1. If compressor is provided, it will be used.

    2. If not, it will use ActiveRecord::Encryption.config.compressor which default value is Zlib.

    If you want to use a custom compressor, it must respond to deflate and inflate.

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 25

def initialize(compress: true, compressor: nil)
  @compress = compress
  @compressor = compressor || ActiveRecord::Encryption.config.compressor
end

Instance Attribute Details

#binary?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 84

def binary?
  serializer.binary?
end

#compress?Boolean (readonly)

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 88

def compress? # :nodoc:
  @compress
end

#compressor (readonly)

The compressor to use for compressing the payload

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 15

attr_reader :compressor

Instance Method Details

#build_encrypted_message(clear_text, key_provider:, cipher_options:) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 111

def build_encrypted_message(clear_text, key_provider:, cipher_options:)
  key = key_provider.encryption_key

  clear_text, was_compressed = compress_if_worth_it(clear_text)
  cipher.encrypt(clear_text, key: key.secret, **cipher_options).tap do |message|
    message.headers.add(key.public_tags)
    message.headers.compressed = true if was_compressed
  end
end

#cipher (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 107

def cipher
  ActiveRecord::Encryption.cipher
end

#compress(data) (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 144

def compress(data)
  @compressor.deflate(data).tap do |compressed_data|
    compressed_data.force_encoding(data.encoding)
  end
end

#compress_if_worth_it(string) (private)

Under certain threshold, ZIP compression is actually worse that not compressing

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 136

def compress_if_worth_it(string)
  if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
    [compress(string), true]
  else
    [string, false]
  end
end

#decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})

Decrypts an encrypted_text and returns the result as clean text

Options

:key_provider

Key provider to use for the encryption operation. It will default to ActiveRecord::Encryption.key_provider when not provided

:cipher_options

Cipher-specific options that will be passed to the Cipher configured in ActiveRecord::Encryption.cipher

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 67

def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
  message = deserialize_message(encrypted_text)
  keys = key_provider.decryption_keys(message)
  raise Errors::Decryption unless keys.present?
  uncompress_if_needed(cipher.decrypt(message, key: keys.collect(&:secret), **cipher_options), message.headers.compressed)
rescue *(ENCODING_ERRORS + DECRYPT_ERRORS)
  raise Errors::Decryption
end

#default_key_provider (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 97

def default_key_provider
  ActiveRecord::Encryption.key_provider
end

#deserialize_message(message) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 125

def deserialize_message(message)
  serializer.load message
rescue ArgumentError, TypeError, Errors::ForbiddenClass
  raise Errors::Encoding
end

#encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})

Encrypts clean_text and returns the encrypted result

Internally, it will:

  1. Create a new Message

  2. Compress and encrypt clean_text as the message payload

  3. Serialize it with ActiveRecord::Encryption.message_serializer (ActiveRecord::Encryption::SafeMarshal by default)

  4. Encode the result with ::ActiveRecord::Base 64

Options

:key_provider

Key provider to use for the encryption operation. It will default to ActiveRecord::Encryption.key_provider when not provided.

:cipher_options

Cipher-specific options that will be passed to the Cipher configured in ActiveRecord::Encryption.cipher

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 49

def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
  clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]

  validate_payload_type(clear_text)
  serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
end

#encrypted?(text) ⇒ Boolean

Returns whether the text is encrypted or not

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 77

def encrypted?(text)
  deserialize_message(text)
  true
rescue Errors::Encoding, *DECRYPT_ERRORS
  false
end

#force_encoding_if_needed(value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 164

def force_encoding_if_needed(value)
  if forced_encoding_for_deterministic_encryption && value && value.encoding != forced_encoding_for_deterministic_encryption
    value.encode(forced_encoding_for_deterministic_encryption, invalid: :replace, undef: :replace)
  else
    value
  end
end

#forced_encoding_for_deterministic_encryption (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 172

def forced_encoding_for_deterministic_encryption
  ActiveRecord::Encryption.config.forced_encoding_for_deterministic_encryption
end

#serialize_message(message) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 121

def serialize_message(message)
  serializer.dump(message)
end

#serializer (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 131

def serializer
  ActiveRecord::Encryption.message_serializer
end

#uncompress(data) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 158

def uncompress(data)
  @compressor.inflate(data).tap do |uncompressed_data|
    uncompressed_data.force_encoding(data.encoding)
  end
end

#uncompress_if_needed(data, compressed) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 150

def uncompress_if_needed(data, compressed)
  if compressed
    uncompress(data)
  else
    data
  end
end

#validate_payload_type(clear_text) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 101

def validate_payload_type(clear_text)
  unless clear_text.is_a?(String)
    raise ActiveRecord::Encryption::Errors::ForbiddenClass, "The encryptor can only encrypt string values (#{clear_text.class})"
  end
end