class ActiveRecord::Encryption::Encryptor

ActiveRecord::Encryption::Cipher the actual encryption algorithm.
It interacts with a KeyProvider for getting the keys, and delegate to
uses for encrypting and decrypting attribute values.
An encryptor exposes the encryption API that ActiveRecord::Encryption::EncryptedAttributeType

def build_encrypted_message(clear_text, key_provider:, cipher_options:)

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

def cipher

def cipher
  ActiveRecord::Encryption.cipher
end

def compress(data)

def compress(data)
  Zlib::Deflate.deflate(data).tap do |compressed_data|
    compressed_data.force_encoding(data.encoding)
  end
end

def compress_if_worth_it(string)

Under certain threshold, ZIP compression is actually worse that not compressing
def compress_if_worth_it(string)
  if string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
    [compress(string), true]
  else
    [string, false]
  end
end

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

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

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

=== Options

Decrypts a +clean_text+ and returns the result as clean text
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

def default_key_provider

def default_key_provider
  ActiveRecord::Encryption.key_provider
end

def deserialize_message(message)

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

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

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

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

=== Options

4. Encode the result with Base 64
by default)
3. Serialize it with +ActiveRecord::Encryption.message_serializer+ (+ActiveRecord::Encryption::SafeMarshal+
2. Compress and encrypt +clean_text+ as the message payload
1. Create a new ActiveRecord::Encryption::Message

Internally, it will:

Encrypts +clean_text+ and returns the encrypted result
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

def encrypted?(text)

Returns whether the text is encrypted or not
def encrypted?(text)
  deserialize_message(text)
  true
rescue Errors::Encoding, *DECRYPT_ERRORS
  false
end

def force_encoding_if_needed(value)

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

def forced_encoding_for_deterministic_encryption

def forced_encoding_for_deterministic_encryption
  ActiveRecord::Encryption.config.forced_encoding_for_deterministic_encryption
end

def serialize_message(message)

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

def serializer

def serializer
  ActiveRecord::Encryption.message_serializer
end

def uncompress(data)

def uncompress(data)
  Zlib::Inflate.inflate(data).tap do |uncompressed_data|
    uncompressed_data.force_encoding(data.encoding)
  end
end

def uncompress_if_needed(data, compressed)

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

def validate_payload_type(clear_text)

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