Method: AttrEncrypted#attr_encrypted

Defined in:
lib/attr_encrypted.rb

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

Generates attr_accessors that encrypt and decrypt attributes transparently

Options (any other options you specify are passed to the encryptor’s encrypt and decrypt methods)

:attribute        => The name of the referenced encrypted attribute. For example
                     <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
                     attribute named 'ee' to store the encrypted email. This is useful when defining
                     one attribute to encrypt at a time or when the :prefix and :suffix options
                     aren't enough. Defaults to nil.

:prefix           => A prefix used to generate the name of the referenced encrypted attributes.
                     For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
                     generate attributes named 'crypted_email' and 'crypted_password' to store the
                     encrypted email and password. Defaults to 'encrypted_'.

:suffix           => A suffix used to generate the name of the referenced encrypted attributes.
                     For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
                     would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
                     encrypted email. Defaults to ''.

:key              => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
                     a symbol representing an instance method then the :key option will be replaced with the result of the
                     method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
                     Any other key types will be passed directly to the encryptor.

:encode           => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
                     planning on storing the encrypted attributes in a database. The default encoding is 'm' (base64),
                     however this can be overwritten by setting the :encode option to some other encoding string instead of
                     just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
                     Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.

: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 unless you're using it with ActiveRecord
                     or DataMapper.

: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 default IV and salt of the underlying encryptor object
                     is used; <tt>:per_attribute_iv_and_salt</tt> uses a per-attribute IV and salt attribute and
                     is the recommended mode for new deployments.
                     Defaults to <tt>:single_iv_and_salt</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, :credit_card, :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


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
# File 'lib/attr_encrypted.rb', line 105

def attr_encrypted(*attributes)
  options = {
    :prefix           => 'encrypted_',
    :suffix           => '',
    :if               => true,
    :unless           => false,
    :encode           => false,
    :default_encoding => 'm',
    :marshal          => false,
    :marshaler        => Marshal,
    :dump_method      => 'dump',
    :load_method      => 'load',
    :encryptor        => Encryptor,
    :encrypt_method   => 'encrypt',
    :decrypt_method   => 'decrypt',
    :mode             => :single_iv_and_salt
  }.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})

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

  attributes.each do |attribute|
    encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
    iv_name = "#{encrypted_attribute_name}_iv".to_sym
    salt_name = "#{encrypted_attribute_name}_salt".to_sym

    instance_methods_as_symbols = instance_methods.collect { |method| method.to_sym }
    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}=")

    if options[:mode] == :per_attribute_iv_and_salt
      attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
      attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")

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

    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