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
-
DECRYPT_ERRORS =
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 95[OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
-
ENCODING_ERRORS =
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 96[EncodingError, Errors::Encoding]
-
THRESHOLD_TO_JUSTIFY_COMPRESSION =
This threshold cannot be changed.
Users can search for attributes encrypted with
deterministic: true
. That is possible because we are able to generate the message for the given clear text deterministically, and with that perform a regular string lookup in SQL.Problem is, messages may have a “c” header that is present or not depending on whether compression was applied on encryption. If this threshold was modified, the message generated for lookup could vary for the same clear text, and searches on exisiting data could fail.
140.bytes
Class Method Summary
-
.new(compress: true, compressor: nil) ⇒ Encryptor
constructor
Options.
Instance Attribute Summary
- #binary? ⇒ Boolean readonly
-
#compressor
readonly
The compressor to use for compressing the payload.
- #compress? ⇒ Boolean readonly Internal use only
Instance Method Summary
-
#decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
Decrypts an
encrypted_text
and returns the result as clean text. -
#encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
Encrypts
clean_text
and returns the encrypted result. -
#encrypted?(text) ⇒ Boolean
Returns whether the text is encrypted or not.
- #build_encrypted_message(clear_text, key_provider:, cipher_options:) private
- #cipher private
- #compress(data) readonly private
-
#compress_if_worth_it(string)
private
Under certain threshold, ZIP compression is actually worse that not compressing.
- #default_key_provider private
- #deserialize_message(message) private
- #force_encoding_if_needed(value) private
- #forced_encoding_for_deterministic_encryption private
- #serialize_message(message) private
- #serializer private
- #uncompress(data) private
- #uncompress_if_needed(data, compressed) private
- #validate_payload_type(clear_text) private
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. It must respond to
deflate
andinflate
. If not provided, will default toActiveRecord::Encryption.config.compressor
, which itself defaults toZlib
.
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 27
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 86
def binary? serializer.binary? end
#compress? ⇒ Boolean
(readonly)
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 90
def compress? # :nodoc: @compress end
#compressor (readonly)
The compressor to use for compressing the payload.
# 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 125
def (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, ** ).tap do || .headers.add(key. ) .headers.compressed = true if was_compressed end end
#cipher (private)
[ GitHub ]# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 121
def cipher ActiveRecord::Encryption.cipher end
#compress(data) (readonly, private)
[ GitHub ]# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 158
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
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 150
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
.
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 69
def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {}) = (encrypted_text) keys = key_provider.decryption_keys( ) raise Errors::Decryption unless keys.present? uncompress_if_needed(cipher.decrypt(, key: keys.collect(&:secret), ** ), .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 111
def default_key_provider ActiveRecord::Encryption.key_provider end
#deserialize_message(message) (private)
[ GitHub ]# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 139
def ( ) serializer.load 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:
-
Create a new
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 Base64.
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
.
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 51
def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {}) clear_text = force_encoding_if_needed(clear_text) if [:deterministic] validate_payload_type(clear_text) (clear_text, key_provider: key_provider, cipher_options: ) end
#encrypted?(text) ⇒ Boolean
Returns whether the text is encrypted or not.
# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 79
def encrypted?(text) (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 178
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 186
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 135
def ( ) serializer.dump( ) end
#serializer (private)
[ GitHub ]# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 145
def serializer ActiveRecord::Encryption. end
#uncompress(data) (private)
[ GitHub ]# File 'activerecord/lib/active_record/encryption/encryptor.rb', line 172
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 164
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 115
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