Module: EncryptedAttributes::MacroMethods

Defined in:
lib/encrypted_attributes.rb

Instance Method Summary collapse

Instance Method Details

#encrypts(attr_name, options = {}, &config) ⇒ Object

Encrypts the given attribute.

Configuration options:

  • :mode - The mode of encryption to use. Default is :sha. See EncryptedStrings for other possible modes.

  • :to - The attribute to write the encrypted value to. Default is the same attribute being encrypted.

  • :before - The callback to invoke every time before the attribute is encrypted

  • :after - The callback to invoke every time after the attribute is encrypted

  • :on - The ActiveRecord callback to use when triggering the encryption. By default, this will encrypt on before_validation. See ActiveRecord::Callbacks for a list of possible callbacks.

  • :if - Specifies a method, proc or string to call to determine if the encryption should occur. The method, proc or string should return or evaluate to a true or false value.

  • :unless - Specifies a method, proc or string to call to determine if the encryption should not occur. The method, proc or string should return or evaluate to a true or false value.

For additional configuration options used during the actual encryption, see the individual cipher class for the specified mode.

Encryption timeline

By default, attributes are encrypted immediately before a record is validated. This means that you can still validate the presence of the encrypted attribute, but other things like password length cannot be validated without either (a) decrypting the value first or (b) using a different encryption target. For example,

class User < ActiveRecord::Base
  encrypts :password, :to => :crypted_password

  validates_presence_of :password, :crypted_password
  validates_length_of :password, :maximum => 16
end

In the above example, the actual encrypted password will be stored in the crypted_password attribute. This means that validations can still run against the model for the original password value.

user = User.new(:password => 'secret')
user.password             # => "secret"
user.crypted_password     # => nil
user.valid?               # => true
user.crypted_password     # => "8152bc582f58c854f580cb101d3182813dec4afe"

user = User.new(:password => 'longer_than_the_maximum_allowed')
user.valid?               # => false
user.crypted_password     # => "e80a709f25798f87d9ca8005a7f64a645964d7c2"
user.errors[:password]    # => "is too long (maximum is 16 characters)"

Encryption mode examples

SHA encryption:

class User < ActiveRecord::Base
  encrypts :password
  # encrypts :password, :salt => 'secret'
end

Symmetric encryption:

class User < ActiveRecord::Base
  encrypts :password, :mode => :symmetric
  # encrypts :password, :mode => :symmetric, :key => 'custom'
end

Asymmetric encryption:

class User < ActiveRecord::Base
  encrypts :password, :mode => :asymmetric
  # encrypts :password, :mode => :asymmetric, :public_key_file => '/keys/public', :private_key_file => '/keys/private'
end

Dynamic configuration

For better security, the encryption options (such as the salt value) can be based on values in each individual record. In order to dynamically configure the encryption options so that individual records can be referenced, an optional block can be specified.

For example,

class User < ActiveRecord::Base
  encrypts :password, :mode => :sha, :before => :create_salt do |user|
    {:salt => user.salt}
  end

  private
    def create_salt
      self.salt = "#{}-#{Time.now}"
    end
end

In the above example, the SHA encryption’s salt is configured dynamically based on the user’s login and the time at which it was encrypted. This helps improve the security of the user’s password.



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
# File 'lib/encrypted_attributes.rb', line 106

def encrypts(attr_name, options = {}, &config)
  config ||= options
  attr_name = attr_name.to_s
  to_attr_name = (options.delete(:to) || attr_name).to_s
  
  # Figure out what cipher is being configured for the attribute
  mode = options.delete(:mode) || :sha
  class_name = "#{mode.to_s.classify}Cipher"
  if EncryptedAttributes.const_defined?(class_name)
    cipher_class = EncryptedAttributes.const_get(class_name)
  else
    cipher_class = EncryptedStrings.const_get(class_name)
  end
  
  # Define encryption hooks
  define_callbacks("before_encrypt_#{attr_name}", "after_encrypt_#{attr_name}")
  send("before_encrypt_#{attr_name}", options.delete(:before)) if options.include?(:before)
  send("after_encrypt_#{attr_name}", options.delete(:after)) if options.include?(:after)
  
  # Set the encrypted value on the configured callback
  callback = options.delete(:on) || :before_validation
  send(callback, :if => options.delete(:if), :unless => options.delete(:unless)) do |record|
    record.send(:write_encrypted_attribute, attr_name, to_attr_name, cipher_class, config)
    true
  end
  
  # Define virtual source attribute
  if attr_name != to_attr_name && !column_names.include?(attr_name)
    attr_reader attr_name unless method_defined?(attr_name)
    attr_writer attr_name unless method_defined?("#{attr_name}=")
  end
  
  # Define the reader when reading the encrypted attribute from the database
  define_method(to_attr_name) do
    read_encrypted_attribute(to_attr_name, cipher_class, config)
  end
  
  unless included_modules.include?(EncryptedAttributes::InstanceMethods)
    include EncryptedAttributes::InstanceMethods
  end
end