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)
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: {})
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: {})
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)
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