Module: AttrEncrypted

Defined in:
lib/attr_encrypted.rb,
lib/attr_encrypted/version.rb,
lib/attr_encrypted/adapters/sequel.rb,
lib/attr_encrypted/adapters/data_mapper.rb,
lib/attr_encrypted/adapters/active_record.rb

Overview

Adds attr_accessors that encrypt and decrypt an object’s attributes

Defined Under Namespace

Modules: Adapters, InstanceMethods, Version

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *arguments, &block) ⇒ Object

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')


291
292
293
294
295
296
297
# File 'lib/attr_encrypted.rb', line 291

def method_missing(method, *arguments, &block)
  if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
    send($1, $3, *arguments)
  else
    super
  end
end

Class Method Details

.extended(base) ⇒ Object

:nodoc:



7
8
9
10
11
12
13
# File 'lib/attr_encrypted.rb', line 7

def self.extended(base) # :nodoc:
  base.class_eval do
    include InstanceMethods
    attr_writer :attr_encrypted_options
    @attr_encrypted_options, @encrypted_attributes = {}, {}
  end
end

Instance Method Details

#attr_encrypted(*attributes) ⇒ Object Also known as: attr_encryptor

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>.

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 = '[email protected]'
@user.email? # true
@user.encrypted_email # returns the encrypted version of '[email protected]'

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

See README for more examples


131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/attr_encrypted.rb', line 131

def attr_encrypted(*attributes)
  options = attributes.last.is_a?(Hash) ? attributes.pop : {}
  options = attr_encrypted_default_options.dup.merge!(attr_encrypted_options).merge!(options)

  options[:encode] = options[:default_encoding] if options[:encode] == true
  options[:encode_iv] = options[:default_encoding] if options[:encode_iv] == true
  options[:encode_salt] = options[:default_encoding] if options[:encode_salt] == true

  attributes.each do |attribute|
    encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym

    instance_methods_as_symbols = attribute_instance_methods_as_symbols
    attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
    attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")

    iv_name = "#{encrypted_attribute_name}_iv".to_sym
    attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
    attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")

    salt_name = "#{encrypted_attribute_name}_salt".to_sym
    attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name)
    attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")

    define_method(attribute) do
      instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
    end

    define_method("#{attribute}=") do |value|
      send("#{encrypted_attribute_name}=", encrypt(attribute, value))
      instance_variable_set("@#{attribute}", value)
    end

    define_method("#{attribute}?") do
      value = send(attribute)
      value.respond_to?(:empty?) ? !value.empty? : !!value
    end

    encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
  end
end

#attr_encrypted?(attribute) ⇒ Boolean

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

Returns:

  • (Boolean)


216
217
218
# File 'lib/attr_encrypted.rb', line 216

def attr_encrypted?(attribute)
  encrypted_attributes.has_key?(attribute.to_sym)
end

#attr_encrypted_optionsObject

Default options to use with calls to attr_encrypted

It will inherit existing options from its superclass



177
178
179
# File 'lib/attr_encrypted.rb', line 177

def attr_encrypted_options
  @attr_encrypted_options ||= superclass.attr_encrypted_options.dup
end

#decrypt(attribute, encrypted_value, options = {}) ⇒ Object

Decrypts a value for the attribute specified

Example

class User
  attr_encrypted :email
end

email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')


229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/attr_encrypted.rb', line 229

def decrypt(attribute, encrypted_value, options = {})
  options = encrypted_attributes[attribute.to_sym].merge(options)
  if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
    encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
    value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
    if options[:marshal]
      value = options[:marshaler].send(options[:load_method], value)
    elsif defined?(Encoding)
      encoding = Encoding.default_internal || Encoding.default_external
      value = value.force_encoding(encoding.name)
    end
    value
  else
    encrypted_value
  end
end

#encrypt(attribute, value, options = {}) ⇒ Object

Encrypts a value for the attribute specified

Example

class User
  attr_encrypted :email
end

encrypted_email = User.encrypt(:email, '[email protected]')


255
256
257
258
259
260
261
262
263
264
265
# File 'lib/attr_encrypted.rb', line 255

def encrypt(attribute, value, options = {})
  options = encrypted_attributes[attribute.to_sym].merge(options)
  if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
    value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
    encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(value: value))
    encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
    encrypted_value
  else
    value
  end
end

#encrypted_attributesObject

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' } }


277
278
279
# File 'lib/attr_encrypted.rb', line 277

def encrypted_attributes
  @encrypted_attributes ||= superclass.encrypted_attributes.dup
end