Module: AttrEncryptor

Defined in:
lib/attr_encryptor.rb,
lib/attr_encryptor/version.rb,
lib/attr_encryptor/adapters/sequel.rb,
lib/attr_encryptor/adapters/data_mapper.rb,
lib/attr_encryptor/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')


259
260
261
262
263
264
265
# File 'lib/attr_encryptor.rb', line 259

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:



8
9
10
11
12
13
14
# File 'lib/attr_encryptor.rb', line 8

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

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.
:charset          => Forces the decrypted string to be interpreted as the specified encoding. Does not change the underlying bits.
                     Use :default to use Ruby's default encoding.

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


102
103
104
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
159
160
161
# File 'lib/attr_encryptor.rb', line 102

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',
    :charset          => :default
  }.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})

  options[:encode] = options[:default_encoding] if options[:encode] == true
  options[:charset] = Encoding.default_internal if options[:charset] == :default

  attributes.each do |attribute|
    encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).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}=")

    attr_reader (encrypted_attribute_name.to_s + "_iv").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_iv").to_sym )
    attr_writer (encrypted_attribute_name.to_s + "_iv").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_iv").to_sym )

    attr_reader (encrypted_attribute_name.to_s + "_salt").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_salt").to_sym )
    attr_writer (encrypted_attribute_name.to_s + "_salt").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_salt").to_sym )



    define_method(attribute) do

      load_iv_for_attribute(attribute,encrypted_attribute_name, options[:algorithm])
      load_salt_for_attribute(attribute,encrypted_attribute_name)

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

    define_method("#{attribute}=") do |value|
      load_iv_for_attribute(attribute, encrypted_attribute_name, options[:algorithm])
      load_salt_for_attribute(attribute, encrypted_attribute_name)

      #this add's the iv and salt on the options for this instance
      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)


183
184
185
# File 'lib/attr_encryptor.rb', line 183

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



168
169
170
# File 'lib/attr_encryptor.rb', line 168

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


196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/attr_encryptor.rb', line 196

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))
    value = options[:marshaler].send(options[:load_method], value) if options[:marshal]
    return_value = value
  else
    return_value = encrypted_value
  end

  if RUBY_VERSION > '1.9' && options[:charset].present? && return_value.present? && return_value.is_a?(String)
    return_value.force_encoding(options[:charset])
  end

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


223
224
225
226
227
228
229
230
231
232
233
# File 'lib/attr_encryptor.rb', line 223

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


245
246
247
# File 'lib/attr_encryptor.rb', line 245

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