Top Level Namespace

Defined Under Namespace

Modules: ActiveRecord, SymmetricEncryption Classes: SymmetricEncryptionValidator

Instance Method Summary collapse

Instance Method Details

#encryptedField

Add :encrypted option for Mongoid models

Example:

require 'mongoid'
require 'symmetric-encryption'

# Initialize Mongoid in a standalone environment. In a Rails app this is not required
Mongoid.logger = Logger.new($stdout)
Mongoid.load!('config/mongoid.yml')

# Initialize SymmetricEncryption in a standalone environment. In a Rails app this is not required
SymmetricEncryption.load!('config/symmetric-encryption.yml', 'test')

class Person
  include Mongoid::Document

  field :name,                             :type => String
  field :encrypted_social_security_number, :type => String, :encrypted => true
  field :date_of_birth,                    :type => Date
  field :encrypted_life_history,           :type => String, :encrypted => {:compress => true, :random_iv => true}

  # Encrypted fields are _always_ stored in Mongo as a String
  # To get the result back as an Integer, Symmetric Encryption can do the
  # necessary conversions by specifying the internal type as an option
  # to :encrypted
  # #see SymmetricEncryption::COERCION_TYPES for full list of types
  field :encrypted_age,                    :type => String, :encrypted => {:type => :integer, :random_iv => true}
end

The above document results in the following document in the Mongo collection ‘persons’:

"name" : "Joe",
"encrypted_social_security_number" : "...",
"age"  : 21
"encrypted_life_history" : "...",

Symmetric Encryption creates the getters and setters to be able to work with the field in it’s unencrypted form. For example

Example:

person = Person.where(:encrypted_social_security_number => '...').first

puts "Decrypted Social Security Number is: #{person.social_security_number}"

# Or is the same as
puts "Decrypted Social Security Number is: #{SymmetricEncryption.decrypt(person.encrypted_social_security_number)}"

# Sets the encrypted_social_security_number to encrypted version
person.social_security_number = "123456789"

# Or, is equivalent to:
person.encrypted_social_security_number = SymmetricEncryption.encrypt("123456789")

Note: Only “String” types are currently supported for encryption

Note: Unlike attr_encrypted finders must use the encrypted field name

Invalid Example, does not work:
  person = Person.where(:social_security_number => '123456789').first

Valid Example:
  person = Person.where(:encrypted_social_security_number => SymmetricEncryption.encrypt('123456789')).first

Defines all the fields that are accessible on the Document For each field that is defined, a getter and setter will be added as an instance method to the Document.

Some of the other regular Mongoid options:

Examples:

Define a field.

field :social_security_number, :type => String, :encrypted => {:compress => false, :random_iv => false}
field :sensitive_text, :type => String, :encrypted => {:compress => true, :random_iv => true}

Parameters:

  • name (Symbol)

    The name of the field.

  • options (Hash)

    The options to pass to the field.

Returns:

  • (Field)

    The generated field



90
91
92
93
94
95
96
97
98
99
100
101
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
# File 'lib/symmetric_encryption/mongoid.rb', line 90

Mongoid::Fields.option :encrypted do |model, field, options|
  if options != false
    options = options.is_a?(Hash) ? options.dup : {}
    encrypted_field_name = field.name

    decrypted_field_name = options.delete(:decrypt_as)
    if decrypted_field_name.nil? && encrypted_field_name.to_s.start_with?('encrypted_')
      decrypted_field_name = encrypted_field_name.to_s['encrypted_'.length..-1]
    end

    if decrypted_field_name.nil?
      raise "SymmetricEncryption for Mongoid. Encryption enabled for field #{encrypted_field_name}. It must either start with 'encrypted_' or the option :decrypt_as must be supplied"
    end

    random_iv = options.delete(:random_iv) || false
    compress  = options.delete(:compress) || false
    type      = options.delete(:type) || :string
    raise "Invalid type: #{type.inspect}. Valid types: #{SymmetricEncryption::COERCION_TYPES.inspect}" unless SymmetricEncryption::COERCION_TYPES.include?(type)

    options.each {|option| warn "Ignoring unknown option #{option.inspect} supplied to Mongoid :encrypted for #{model}##{field}"}

    if model.const_defined?(:EncryptedAttributes, _search_ancestors = false)
      mod = model.const_get(:EncryptedAttributes)
    else
      mod = model.const_set(:EncryptedAttributes, Module.new)
      model.send(:include, mod)
    end

    # Generate getter and setter methods
    mod.module_eval("      # Set the un-encrypted field\n      # Also updates the encrypted field with the encrypted value\n      # Freeze the decrypted field value so that it is not modified directly\n      def \#{decrypted_field_name}=(value)\n        v = SymmetricEncryption::coerce(value, :\#{type})\n        self.\#{encrypted_field_name} = @stored_\#{encrypted_field_name} = ::SymmetricEncryption.encrypt(v,\#{random_iv},\#{compress},:\#{type})\n        @\#{decrypted_field_name} = v.freeze\n      end\n\n      # Returns the decrypted value for the encrypted field\n      # The decrypted value is cached and is only decrypted if the encrypted value has changed\n      # If this method is not called, then the encrypted value is never decrypted\n      def \#{decrypted_field_name}\n        if @stored_\#{encrypted_field_name} != self.\#{encrypted_field_name}\n          @\#{decrypted_field_name} = ::SymmetricEncryption.decrypt(self.\#{encrypted_field_name},version=nil,:\#{type}).freeze\n          @stored_\#{encrypted_field_name} = self.\#{encrypted_field_name}\n        end\n        @\#{decrypted_field_name}\n      end\n    EOS\n  end\nend\n", __FILE__, __LINE__ + 1)