class ActiveRecord::Encryption::Encryptor
An encryptor exposes the encryption API that ActiveRecord::Encryption::EncryptedAttributeType
uses for encrypting and decrypting attribute values.
It interacts with a KeyProvider
for getting the keys, and delegate to ActiveRecord::Encryption::Cipher
the actual encryption algorithm.
Constants
- DECRYPT_ERRORS
- ENCODING_ERRORS
- THRESHOLD_TO_JUSTIFY_COMPRESSION
Attributes
The compressor to use for compressing the payload
Public Class Methods
Options¶ ↑
-
:compress
- Boolean indicating whether records should be compressed before encryption. Defaults totrue
. -
:compressor
- The compressor to use.-
If compressor is provided, it will be used.
-
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
andinflate
. -
# File lib/active_record/encryption/encryptor.rb, line 25 def initialize(compress: true, compressor: nil) @compress = compress @compressor = compressor || ActiveRecord::Encryption.config.compressor end
Public Instance Methods
# File lib/active_record/encryption/encryptor.rb, line 84 def binary? serializer.binary? end
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 toActiveRecord::Encryption.key_provider
when not provided - :cipher_options
-
Cipher-specific options that will be passed to the
Cipher
configured inActiveRecord::Encryption.cipher
# File 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
Encrypts clean_text
and returns the encrypted result
Internally, it will:
-
Create a new
ActiveRecord::Encryption::Message
-
Compress and encrypt
clean_text
as the message payload -
Serialize it with
ActiveRecord::Encryption.message_serializer
(ActiveRecord::Encryption::SafeMarshal
by default) -
Encode the result with
Base
64
Options¶ ↑
- :key_provider
-
Key
provider to use for the encryption operation. It will default toActiveRecord::Encryption.key_provider
when not provided. - :cipher_options
-
Cipher-specific options that will be passed to the
Cipher
configured inActiveRecord::Encryption.cipher
# File 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
Returns whether the text is encrypted or not
# File lib/active_record/encryption/encryptor.rb, line 77 def encrypted?(text) deserialize_message(text) true rescue Errors::Encoding, *DECRYPT_ERRORS false end
Private Instance Methods
# File 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
# File lib/active_record/encryption/encryptor.rb, line 107 def cipher ActiveRecord::Encryption.cipher end
# File 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
Under certain threshold, ZIP compression is actually worse that not compressing
# File 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
# File lib/active_record/encryption/encryptor.rb, line 97 def default_key_provider ActiveRecord::Encryption.key_provider end
# File lib/active_record/encryption/encryptor.rb, line 125 def deserialize_message(message) serializer.load message rescue ArgumentError, TypeError, Errors::ForbiddenClass raise Errors::Encoding end
# File 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
# File lib/active_record/encryption/encryptor.rb, line 172 def forced_encoding_for_deterministic_encryption ActiveRecord::Encryption.config.forced_encoding_for_deterministic_encryption end
# File lib/active_record/encryption/encryptor.rb, line 121 def serialize_message(message) serializer.dump(message) end
# File lib/active_record/encryption/encryptor.rb, line 131 def serializer ActiveRecord::Encryption.message_serializer end
# File 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
# File lib/active_record/encryption/encryptor.rb, line 150 def uncompress_if_needed(data, compressed) if compressed uncompress(data) else data end end
# File 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