module AttrEncrypted

Adds attr_accessors that encrypt and decrypt an object's attributes

Public Instance Methods

attr_encrypted(*attributes) click to toggle source
encode_salt:          Defaults to true.

default_encoding:     Defaults to 'm' (base64).

marshal:              If set to true, attributes will be marshaled as well
                      as encrypted. This is useful if you're planning on
                      encrypting something other than a string.
                      Defaults to false.

marshaler:            The object to use for marshaling.
                      Defaults to Marshal.

dump_method:          The dump method name to call on the <tt>:marshaler</tt> object to.
                      Defaults to 'dump'.

load_method:          The load method name to call on the <tt>:marshaler</tt> object.
                      Defaults to 'load'.

encryptor:            The object to use for encrypting.
                      Defaults to Encryptor.

encrypt_method:       The encrypt method name to call on the <tt>:encryptor</tt> object.
                      Defaults to 'encrypt'.

decrypt_method:       The decrypt method name to call on the <tt>:encryptor</tt> object.
                      Defaults to 'decrypt'.

if:                   Attributes are only encrypted if this option evaluates
                      to true. If you pass a symbol representing an instance
                      method then the result of the method will be evaluated.
                      Any objects that respond to <tt>:call</tt> are evaluated as well.
                      Defaults to true.

unless:               Attributes are only encrypted if this option evaluates
                      to false. If you pass a symbol representing an instance
                      method then the result of the method will be evaluated.
                      Any objects that respond to <tt>:call</tt> are evaluated as well.
                      Defaults to false.

mode:                 Selects encryption mode for attribute: choose <tt>:single_iv_and_salt</tt> for compatibility
                      with the old attr_encrypted API: the IV is derived from the encryption key by the underlying Encryptor class; salt is not used.
                      The <tt>:per_attribute_iv_and_salt</tt> mode uses a per-attribute IV and salt. The salt is used to derive a unique key per attribute.
                      A <tt>:per_attribute_iv</default> mode derives a unique IV per attribute; salt is not used.
                      Defaults to <tt>:per_attribute_iv</tt>.

allow_empty_value:    Attributes which have nil or empty string values will not be encrypted unless this option
                      has a truthy value.

You can specify your own default options

class User
  # Now all attributes will be encoded and marshaled by default
  attr_encrypted_options.merge!(encode: true, marshal: true, some_other_option: true)
  attr_encrypted :configuration, key: 'my secret key'
end

Example

class User
  attr_encrypted :email, key: 'some secret key'
  attr_encrypted :configuration, key: 'some other secret key', marshal: true
end

@user = User.new
@user.encrypted_email # nil
@user.email? # false
@user.email = 'test@example.com'
@user.email? # true
@user.encrypted_email # returns the encrypted version of 'test@example.com'

@user.configuration = { time_zone: 'UTC' }
@user.encrypted_configuration # returns the encrypted version of configuration

See README for more examples
    # File lib/attr_encrypted.rb
134 def attr_encrypted(*attributes)
135   options = attributes.last.is_a?(Hash) ? attributes.pop : {}
136   options = attr_encrypted_default_options.dup.merge!(attr_encrypted_options).merge!(options)
137 
138   options[:encode] = options[:default_encoding] if options[:encode] == true
139   options[:encode_iv] = options[:default_encoding] if options[:encode_iv] == true
140   options[:encode_salt] = options[:default_encoding] if options[:encode_salt] == true
141 
142   attributes.each do |attribute|
143     encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
144 
145     instance_methods_as_symbols = attribute_instance_methods_as_symbols
146 
147     if attribute_instance_methods_as_symbols_available?
148       attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
149       attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
150 
151       iv_name = "#{encrypted_attribute_name}_iv".to_sym
152       attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
153       attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")
154 
155       salt_name = "#{encrypted_attribute_name}_salt".to_sym
156       attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name)
157       attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")
158     end
159 
160     define_method(attribute) do
161       instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
162     end
163 
164     define_method("#{attribute}=") do |value|
165       send("#{encrypted_attribute_name}=", encrypt(attribute, value))
166       instance_variable_set("@#{attribute}", value)
167     end
168 
169     define_method("#{attribute}?") do
170       value = send(attribute)
171       value.respond_to?(:empty?) ? !value.empty? : !!value
172     end
173 
174     encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
175   end
176 end
Also aliased as: attr_encryptor
attr_encrypted?(attribute) click to toggle source

Checks if an attribute is configured with attr_encrypted

Example

class User
  attr_accessor :name
  attr_encrypted :email
end

User.attr_encrypted?(:name)  # false
User.attr_encrypted?(:email) # true
    # File lib/attr_encrypted.rb
223 def attr_encrypted?(attribute)
224   encrypted_attributes.has_key?(attribute.to_sym)
225 end
attr_encrypted_options() click to toggle source

Default options to use with calls to attr_encrypted

It will inherit existing options from its superclass

    # File lib/attr_encrypted.rb
183 def attr_encrypted_options
184   @attr_encrypted_options ||= superclass.attr_encrypted_options.dup
185 end
attr_encryptor(*attributes)
Alias for: attr_encrypted
decrypt(attribute, encrypted_value, options = {}) click to toggle source

Decrypts a value for the attribute specified

Example

class User
  attr_encrypted :email
end

email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
    # File lib/attr_encrypted.rb
236 def decrypt(attribute, encrypted_value, options = {})
237   options = encrypted_attributes[attribute.to_sym].merge(options)
238   if options[:if] && !options[:unless] && not_empty?(encrypted_value)
239     encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
240     value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
241     if options[:marshal]
242       value = options[:marshaler].send(options[:load_method], value)
243     elsif defined?(Encoding)
244       encoding = Encoding.default_internal || Encoding.default_external
245       value = value.force_encoding(encoding.name)
246     end
247     value
248   else
249     encrypted_value
250   end
251 end
encrypt(attribute, value, options = {}) click to toggle source

Encrypts a value for the attribute specified

Example

class User
  attr_encrypted :email
end

encrypted_email = User.encrypt(:email, 'test@example.com')
    # File lib/attr_encrypted.rb
262 def encrypt(attribute, value, options = {})
263   options = encrypted_attributes[attribute.to_sym].merge(options)
264   if options[:if] && !options[:unless] && (options[:allow_empty_value] || not_empty?(value))
265     value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
266     encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(value: value))
267     encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
268     encrypted_value
269   else
270     value
271   end
272 end
encrypted_attributes() click to toggle source

Contains a hash of encrypted attributes with virtual attribute names as keys and their corresponding options as values

Example

class User
  attr_encrypted :email, key: 'my secret key'
end

User.encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
    # File lib/attr_encrypted.rb
288 def encrypted_attributes
289   @encrypted_attributes ||= superclass.encrypted_attributes.dup
290 end
method_missing(method, *arguments, &block) click to toggle source

Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method if attribute was configured with attr_encrypted

Example

class User
  attr_encrypted :email, key: 'my secret key'
end

User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
Calls superclass method
    # File lib/attr_encrypted.rb
302 def method_missing(method, *arguments, &block)
303   if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
304     send($1, $3, *arguments)
305   else
306     super
307   end
308 end
not_empty?(value) click to toggle source
    # File lib/attr_encrypted.rb
274 def not_empty?(value)
275   !value.nil? && !(value.is_a?(String) && value.empty?)
276 end

Protected Instance Methods

attribute_instance_methods_as_symbols() click to toggle source
    # File lib/attr_encrypted.rb
450 def attribute_instance_methods_as_symbols
451   instance_methods.collect { |method| method.to_sym }
452 end
attribute_instance_methods_as_symbols_available?() click to toggle source
    # File lib/attr_encrypted.rb
454 def attribute_instance_methods_as_symbols_available?
455   true
456 end

Private Instance Methods

attr_encrypted_default_options() click to toggle source
    # File lib/attr_encrypted.rb
187 def attr_encrypted_default_options
188   {
189     prefix:            'encrypted_',
190     suffix:            '',
191     if:                true,
192     unless:            false,
193     encode:            false,
194     encode_iv:         true,
195     encode_salt:       true,
196     default_encoding:  'm',
197     marshal:           false,
198     marshaler:         Marshal,
199     dump_method:       'dump',
200     load_method:       'load',
201     encryptor:         Encryptor,
202     encrypt_method:    'encrypt',
203     decrypt_method:    'decrypt',
204     mode:              :per_attribute_iv,
205     algorithm:         'aes-256-gcm',
206     allow_empty_value: false,
207   }
208 end