attr_encrypted 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
- require 'rake'
2
1
  require 'rake/testtask'
3
- require 'rake/rdoctask'
2
+ require 'rdoc/task'
4
3
  require "bundler/gem_tasks"
5
4
 
6
5
  desc 'Test the attr_encrypted gem.'
@@ -19,16 +18,5 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
19
18
  rdoc.rdoc_files.include('lib/**/*.rb')
20
19
  end
21
20
 
22
- if RUBY_VERSION < '1.9.3'
23
- require 'rcov/rcovtask'
24
-
25
- task :rcov do
26
- sh "rcov -o coverage/rcov --exclude '^(?!lib)' " + FileList[ 'test/**/*_test.rb' ].join(' ')
27
- end
28
-
29
- desc 'Default: run unit tests under rcov.'
30
- task :default => :rcov
31
- else
32
- desc 'Default: run unit tests.'
33
- task :default => :test
34
- end
21
+ desc 'Default: run unit tests.'
22
+ task :default => :test
@@ -0,0 +1,60 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib/', __FILE__)
4
+ $:.unshift lib unless $:.include?(lib)
5
+
6
+ require 'attr_encrypted/version'
7
+ require 'date'
8
+
9
+ Gem::Specification.new do |s|
10
+ s.name = 'attr_encrypted'
11
+ s.version = AttrEncrypted::Version.string
12
+ s.date = Date.today
13
+
14
+ s.summary = 'Encrypt and decrypt attributes'
15
+ s.description = 'Generates attr_accessors that encrypt and decrypt attributes transparently'
16
+
17
+ s.authors = ['Sean Huber', 'S. Brent Faulkner', 'William Monk', 'Stephen Aghaulor']
18
+ s.email = ['seah@shuber.io', 'sbfaulkner@gmail.com', 'billy.monk@gmail.com', 'saghaulor@gmail.com']
19
+ s.homepage = 'http://github.com/attr-encrypted/attr_encrypted'
20
+
21
+ s.has_rdoc = false
22
+ s.rdoc_options = ['--line-numbers', '--inline-source', '--main', 'README.rdoc']
23
+
24
+ s.require_paths = ['lib']
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- test/*`.split("\n")
28
+
29
+ s.required_ruby_version = '>= 2.0.0'
30
+
31
+ s.add_dependency('encryptor', ['~> 2.0.0'])
32
+ # support for testing with specific active record version
33
+ activerecord_version = if ENV.key?('ACTIVERECORD')
34
+ "~> #{ENV['ACTIVERECORD']}"
35
+ else
36
+ '>= 2.0.0'
37
+ end
38
+ s.add_development_dependency('activerecord', activerecord_version)
39
+ s.add_development_dependency('actionpack', activerecord_version)
40
+ s.add_development_dependency('datamapper')
41
+ s.add_development_dependency('rake')
42
+ s.add_development_dependency('minitest')
43
+ s.add_development_dependency('sequel')
44
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE.to_sym == :jruby
45
+ s.add_development_dependency('activerecord-jdbcsqlite3-adapter')
46
+ s.add_development_dependency('jdbc-sqlite3', '< 3.8.7') # 3.8.7 is nice and broke
47
+ else
48
+ s.add_development_dependency('sqlite3')
49
+ end
50
+ s.add_development_dependency('dm-sqlite-adapter')
51
+ s.add_development_dependency('simplecov')
52
+ s.add_development_dependency('simplecov-rcov')
53
+ s.add_development_dependency("codeclimate-test-reporter")
54
+
55
+ s.cert_chain = ['certs/saghaulor.pem']
56
+ s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
57
+
58
+ s.post_install_message = "\n\n\nWARNING: Several insecure default options and features have been deprecated in attr_encrypted v2.0.0. Please see the README for more details.\n\n\n"
59
+
60
+ end
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDdDCCAlygAwIBAgIBATANBgkqhkiG9w0BAQUFADBAMRIwEAYDVQQDDAlzYWdo
3
+ YXVsb3IxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
4
+ bTAeFw0xNjAxMTEyMjQyMDFaFw0xNzAxMTAyMjQyMDFaMEAxEjAQBgNVBAMMCXNh
5
+ Z2hhdWxvcjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYD
6
+ Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx0xdQYk2GwCpQ1n/
7
+ n2mPVYHLYqU5TAn/82t5kbqBUWjbcj8tHAi41tJ19+fT/hH0dog8JHvho1zmOr71
8
+ ZIqreJQo60TqP6oE9a5HncUpjqbRp7tOmHo9E+mOW1yT4NiXqFf1YINExQKy2XND
9
+ WPQ+T50ZNUsGMfHFWB4NAymejRWXlOEY3bvKW0UHFeNmouP5he51TjoP8uCc9536
10
+ 4AIWVP/zzzjwrFtC7av7nRw4Y+gX2bQjrkK2k2JS0ejiGzKBIEMJejcI2B+t79zT
11
+ kUQq9SFwp2BrKSIy+4kh4CiF20RT/Hfc1MbvTxSIl/bbIxCYEOhmtHExHi0CoCWs
12
+ YCGCXQIDAQABo3kwdzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU
13
+ SCpVzSBvYbO6B3oT3n3RCZmurG8wHgYDVR0RBBcwFYETc2FnaGF1bG9yQGdtYWls
14
+ LmNvbTAeBgNVHRIEFzAVgRNzYWdoYXVsb3JAZ21haWwuY29tMA0GCSqGSIb3DQEB
15
+ BQUAA4IBAQAeiGdC3e0WiZpm0cF/b7JC6hJYXC9Yv9VsRAWD9ROsLjFKwOhmonnc
16
+ +l/QrmoTjMakYXBCai/Ca3L+k5eRrKilgyITILsmmFxK8sqPJXUw2Jmwk/dAky6x
17
+ hHKVZAofT1OrOOPJ2USoZyhR/VI8epLaD5wUmkVDNqtZWviW+dtRa55aPYjRw5Pj
18
+ wuj9nybhZr+BbEbmZE//2nbfkM4hCuMtxxxilPrJ22aYNmeWU0wsPpDyhPYxOUgU
19
+ ZjeLmnSDiwL6doiP5IiwALH/dcHU67ck3NGf6XyqNwQrrmtPY0mv1WVVL4Uh+vYE
20
+ kHoFzE2no0BfBg78Re8fY69P5yES5ncC
21
+ -----END CERTIFICATE-----
@@ -14,81 +14,107 @@ module AttrEncrypted
14
14
 
15
15
  # Generates attr_accessors that encrypt and decrypt attributes transparently
16
16
  #
17
- # Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
18
- #
19
- # :attribute => The name of the referenced encrypted attribute. For example
20
- # <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
21
- # attribute named 'ee' to store the encrypted email. This is useful when defining
22
- # one attribute to encrypt at a time or when the :prefix and :suffix options
23
- # aren't enough. Defaults to nil.
24
- #
25
- # :prefix => A prefix used to generate the name of the referenced encrypted attributes.
26
- # For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
27
- # generate attributes named 'crypted_email' and 'crypted_password' to store the
28
- # encrypted email and password. Defaults to 'encrypted_'.
29
- #
30
- # :suffix => A suffix used to generate the name of the referenced encrypted attributes.
31
- # For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
32
- # would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
33
- # encrypted email. Defaults to ''.
34
- #
35
- # :key => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
36
- # a symbol representing an instance method then the :key option will be replaced with the result of the
37
- # method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
38
- # Any other key types will be passed directly to the encryptor.
39
- #
40
- # :encode => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
41
- # planning on storing the encrypted attributes in a database. The default encoding is 'm' (base64),
42
- # however this can be overwritten by setting the :encode option to some other encoding string instead of
43
- # just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
44
- # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
45
- #
46
- # :default_encoding => Defaults to 'm' (base64).
47
- #
48
- # :marshal => If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning
49
- # on encrypting something other than a string. Defaults to false unless you're using it with ActiveRecord
50
- # or DataMapper.
51
- #
52
- # :marshaler => The object to use for marshaling. Defaults to Marshal.
53
- #
54
- # :dump_method => The dump method name to call on the <tt>:marshaler</tt> object to. Defaults to 'dump'.
55
- #
56
- # :load_method => The load method name to call on the <tt>:marshaler</tt> object. Defaults to 'load'.
57
- #
58
- # :encryptor => The object to use for encrypting. Defaults to Encryptor.
59
- #
60
- # :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'encrypt'.
61
- #
62
- # :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'decrypt'.
63
- #
64
- # :if => Attributes are only encrypted if this option evaluates to true. If you pass a symbol representing an instance
65
- # method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
66
- # Defaults to true.
67
- #
68
- # :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
69
- # method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
70
- # Defaults to false.
71
- #
72
- # :mode => Selects encryption mode for attribute: choose <tt>:single_iv_and_salt</tt> for compatibility
73
- # with the old attr_encrypted API: the default IV and salt of the underlying encryptor object
74
- # is used; <tt>:per_attribute_iv_and_salt</tt> uses a per-attribute IV and salt attribute and
75
- # is the recommended mode for new deployments.
76
- # Defaults to <tt>:single_iv_and_salt</tt>.
17
+ # Options (any other options you specify are passed to the Encryptor's encrypt and decrypt methods)
18
+ #
19
+ # attribute: The name of the referenced encrypted attribute. For example
20
+ # <tt>attr_accessor :email, attribute: :ee</tt> would generate an
21
+ # attribute named 'ee' to store the encrypted email. This is useful when defining
22
+ # one attribute to encrypt at a time or when the :prefix and :suffix options
23
+ # aren't enough.
24
+ # Defaults to nil.
25
+ #
26
+ # prefix: A prefix used to generate the name of the referenced encrypted attributes.
27
+ # For example <tt>attr_accessor :email, prefix: 'crypted_'</tt> would
28
+ # generate attributes named 'crypted_email' to store the encrypted
29
+ # email and password.
30
+ # Defaults to 'encrypted_'.
31
+ #
32
+ # suffix: A suffix used to generate the name of the referenced encrypted attributes.
33
+ # For example <tt>attr_accessor :email, prefix: '', suffix: '_encrypted'</tt>
34
+ # would generate attributes named 'email_encrypted' to store the
35
+ # encrypted email.
36
+ # Defaults to ''.
37
+ #
38
+ # key: The encryption key. This option may not be required if
39
+ # you're using a custom encryptor. If you pass a symbol
40
+ # representing an instance method then the :key option
41
+ # will be replaced with the result of the method before
42
+ # being passed to the encryptor. Objects that respond
43
+ # to :call are evaluated as well (including procs).
44
+ # Any other key types will be passed directly to the encryptor.
45
+ # Defaults to nil.
46
+ #
47
+ # encode: If set to true, attributes will be encoded as well as
48
+ # encrypted. This is useful if you're planning on storing
49
+ # the encrypted attributes in a database. The default
50
+ # encoding is 'm' (base64), however this can be overwritten
51
+ # by setting the :encode option to some other encoding
52
+ # string instead of just 'true'. See
53
+ # http://www.ruby-doc.org/core/classes/Array.html#M002245
54
+ # for more encoding directives.
55
+ # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
56
+ #
57
+ # encode_iv: Defaults to true.
58
+
59
+ # encode_salt: Defaults to true.
60
+ #
61
+ # default_encoding: Defaults to 'm' (base64).
62
+ #
63
+ # marshal: If set to true, attributes will be marshaled as well
64
+ # as encrypted. This is useful if you're planning on
65
+ # encrypting something other than a string.
66
+ # Defaults to false.
67
+ #
68
+ # marshaler: The object to use for marshaling.
69
+ # Defaults to Marshal.
70
+ #
71
+ # dump_method: The dump method name to call on the <tt>:marshaler</tt> object to.
72
+ # Defaults to 'dump'.
73
+ #
74
+ # load_method: The load method name to call on the <tt>:marshaler</tt> object.
75
+ # Defaults to 'load'.
76
+ #
77
+ # encryptor: The object to use for encrypting.
78
+ # Defaults to Encryptor.
79
+ #
80
+ # encrypt_method: The encrypt method name to call on the <tt>:encryptor</tt> object.
81
+ # Defaults to 'encrypt'.
82
+ #
83
+ # decrypt_method: The decrypt method name to call on the <tt>:encryptor</tt> object.
84
+ # Defaults to 'decrypt'.
85
+ #
86
+ # if: Attributes are only encrypted if this option evaluates
87
+ # to true. If you pass a symbol representing an instance
88
+ # method then the result of the method will be evaluated.
89
+ # Any objects that respond to <tt>:call</tt> are evaluated as well.
90
+ # Defaults to true.
91
+ #
92
+ # unless: Attributes are only encrypted if this option evaluates
93
+ # to false. If you pass a symbol representing an instance
94
+ # method then the result of the method will be evaluated.
95
+ # Any objects that respond to <tt>:call</tt> are evaluated as well.
96
+ # Defaults to false.
97
+ #
98
+ # mode: Selects encryption mode for attribute: choose <tt>:single_iv_and_salt</tt> for compatibility
99
+ # with the old attr_encrypted API: the IV is derived from the encryption key by the underlying Encryptor class; salt is not used.
100
+ # 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.
101
+ # A <tt>:per_attribute_iv</default> mode derives a unique IV per attribute; salt is not used.
102
+ # Defaults to <tt>:per_attribute_iv</tt>.
77
103
  #
78
104
  # You can specify your own default options
79
105
  #
80
106
  # class User
81
- # # now all attributes will be encoded and marshaled by default
82
- # attr_encrypted_options.merge!(:encode => true, :marshal => true, :some_other_option => true)
83
- # attr_encrypted :configuration, :key => 'my secret key'
107
+ # # Now all attributes will be encoded and marshaled by default
108
+ # attr_encrypted_options.merge!(encode: true, marshal: true, some_other_option: true)
109
+ # attr_encrypted :configuration, key: 'my secret key'
84
110
  # end
85
111
  #
86
112
  #
87
113
  # Example
88
114
  #
89
115
  # class User
90
- # attr_encrypted :email, :credit_card, :key => 'some secret key'
91
- # attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
116
+ # attr_encrypted :email, key: 'some secret key'
117
+ # attr_encrypted :configuration, key: 'some other secret key', marshal: true
92
118
  # end
93
119
  #
94
120
  # @user = User.new
@@ -98,46 +124,32 @@ module AttrEncrypted
98
124
  # @user.email? # true
99
125
  # @user.encrypted_email # returns the encrypted version of 'test@example.com'
100
126
  #
101
- # @user.configuration = { :time_zone => 'UTC' }
127
+ # @user.configuration = { time_zone: 'UTC' }
102
128
  # @user.encrypted_configuration # returns the encrypted version of configuration
103
129
  #
104
130
  # See README for more examples
105
131
  def attr_encrypted(*attributes)
106
- options = {
107
- :prefix => 'encrypted_',
108
- :suffix => '',
109
- :if => true,
110
- :unless => false,
111
- :encode => false,
112
- :default_encoding => 'm',
113
- :marshal => false,
114
- :marshaler => Marshal,
115
- :dump_method => 'dump',
116
- :load_method => 'load',
117
- :encryptor => Encryptor,
118
- :encrypt_method => 'encrypt',
119
- :decrypt_method => 'decrypt',
120
- :mode => :single_iv_and_salt
121
- }.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})
132
+ options = attributes.last.is_a?(Hash) ? attributes.pop : {}
133
+ options = attr_encrypted_default_options.dup.merge!(attr_encrypted_options).merge!(options)
122
134
 
123
135
  options[:encode] = options[:default_encoding] if options[:encode] == true
136
+ options[:encode_iv] = options[:default_encoding] if options[:encode_iv] == true
137
+ options[:encode_salt] = options[:default_encoding] if options[:encode_salt] == true
124
138
 
125
139
  attributes.each do |attribute|
126
140
  encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
127
- iv_name = "#{encrypted_attribute_name}_iv".to_sym
128
- salt_name = "#{encrypted_attribute_name}_salt".to_sym
129
141
 
130
142
  instance_methods_as_symbols = attribute_instance_methods_as_symbols
131
143
  attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
132
144
  attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
133
145
 
134
- if options[:mode] == :per_attribute_iv_and_salt
135
- attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
136
- attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")
146
+ iv_name = "#{encrypted_attribute_name}_iv".to_sym
147
+ attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
148
+ attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")
137
149
 
138
- attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name)
139
- attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")
140
- end
150
+ salt_name = "#{encrypted_attribute_name}_salt".to_sym
151
+ attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name)
152
+ attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")
141
153
 
142
154
  define_method(attribute) do
143
155
  instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
@@ -153,7 +165,7 @@ module AttrEncrypted
153
165
  value.respond_to?(:empty?) ? !value.empty? : !!value
154
166
  end
155
167
 
156
- encrypted_attributes[attribute.to_sym] = options.merge(:attribute => encrypted_attribute_name)
168
+ encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
157
169
  end
158
170
  end
159
171
 
@@ -166,6 +178,30 @@ module AttrEncrypted
166
178
  @attr_encrypted_options ||= superclass.attr_encrypted_options.dup
167
179
  end
168
180
 
181
+ def attr_encrypted_default_options
182
+ {
183
+ prefix: 'encrypted_',
184
+ suffix: '',
185
+ if: true,
186
+ unless: false,
187
+ encode: false,
188
+ encode_iv: true,
189
+ encode_salt: true,
190
+ default_encoding: 'm',
191
+ marshal: false,
192
+ marshaler: Marshal,
193
+ dump_method: 'dump',
194
+ load_method: 'load',
195
+ encryptor: Encryptor,
196
+ encrypt_method: 'encrypt',
197
+ decrypt_method: 'decrypt',
198
+ mode: :per_attribute_iv,
199
+ algorithm: 'aes-256-gcm',
200
+ }
201
+ end
202
+
203
+ private :attr_encrypted_default_options
204
+
169
205
  # Checks if an attribute is configured with <tt>attr_encrypted</tt>
170
206
  #
171
207
  # Example
@@ -194,7 +230,7 @@ module AttrEncrypted
194
230
  options = encrypted_attributes[attribute.to_sym].merge(options)
195
231
  if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
196
232
  encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
197
- value = options[:encryptor].send(options[:decrypt_method], options.merge!(:value => encrypted_value))
233
+ value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
198
234
  if options[:marshal]
199
235
  value = options[:marshaler].send(options[:load_method], value)
200
236
  elsif defined?(Encoding)
@@ -220,7 +256,7 @@ module AttrEncrypted
220
256
  options = encrypted_attributes[attribute.to_sym].merge(options)
221
257
  if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
222
258
  value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
223
- encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(:value => value))
259
+ encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(value: value))
224
260
  encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
225
261
  encrypted_value
226
262
  else
@@ -234,10 +270,10 @@ module AttrEncrypted
234
270
  # Example
235
271
  #
236
272
  # class User
237
- # attr_encrypted :email, :key => 'my secret key'
273
+ # attr_encrypted :email, key: 'my secret key'
238
274
  # end
239
275
  #
240
- # User.encrypted_attributes # { :email => { :attribute => 'encrypted_email', :key => 'my secret key' } }
276
+ # User.encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
241
277
  def encrypted_attributes
242
278
  @encrypted_attributes ||= superclass.encrypted_attributes.dup
243
279
  end
@@ -248,7 +284,7 @@ module AttrEncrypted
248
284
  # Example
249
285
  #
250
286
  # class User
251
- # attr_encrypted :email, :key => 'my secret key'
287
+ # attr_encrypted :email, key: 'my secret key'
252
288
  # end
253
289
  #
254
290
  # User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
@@ -267,7 +303,7 @@ module AttrEncrypted
267
303
  #
268
304
  # class User
269
305
  # attr_accessor :secret_key
270
- # attr_encrypted :email, :key => :secret_key
306
+ # attr_encrypted :email, key: :secret_key
271
307
  #
272
308
  # def initialize(secret_key)
273
309
  # self.secret_key = secret_key
@@ -277,6 +313,7 @@ module AttrEncrypted
277
313
  # @user = User.new('some-secret-key')
278
314
  # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
279
315
  def decrypt(attribute, encrypted_value)
316
+ encrypted_attributes[attribute.to_sym][:operation] = :decrypting
280
317
  self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
281
318
  end
282
319
 
@@ -286,7 +323,7 @@ module AttrEncrypted
286
323
  #
287
324
  # class User
288
325
  # attr_accessor :secret_key
289
- # attr_encrypted :email, :key => :secret_key
326
+ # attr_encrypted :email, key: :secret_key
290
327
  #
291
328
  # def initialize(secret_key)
292
329
  # self.secret_key = secret_key
@@ -296,19 +333,38 @@ module AttrEncrypted
296
333
  # @user = User.new('some-secret-key')
297
334
  # @user.encrypt(:email, 'test@example.com')
298
335
  def encrypt(attribute, value)
336
+ encrypted_attributes[attribute.to_sym][:operation] = :encrypting
299
337
  self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
300
338
  end
301
339
 
340
+ # Copies the class level hash of encrypted attributes with virtual attribute names as keys
341
+ # and their corresponding options as values to the instance
342
+ #
343
+ def encrypted_attributes
344
+ @encrypted_attributes ||= self.class.encrypted_attributes.dup
345
+ end
346
+
302
347
  protected
303
348
 
304
349
  # Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
305
350
  def evaluated_attr_encrypted_options_for(attribute)
306
- if evaluate_attr_encrypted_option(self.class.encrypted_attributes[attribute.to_sym][:mode]) == :per_attribute_iv_and_salt
307
- load_iv_for_attribute(attribute, self.class.encrypted_attributes[attribute.to_sym][:algorithm])
308
- load_salt_for_attribute(attribute)
351
+ evaluated_options = Hash.new
352
+ attribute_option_value = encrypted_attributes[attribute.to_sym][:attribute]
353
+ self.class.encrypted_attributes[attribute.to_sym].map do |option, value|
354
+ evaluated_options[option] = evaluate_attr_encrypted_option(value)
309
355
  end
310
356
 
311
- self.class.encrypted_attributes[attribute.to_sym].inject({}) { |hash, (option, value)| hash[option] = evaluate_attr_encrypted_option(value); hash }
357
+ evaluated_options[:attribute] = attribute_option_value
358
+
359
+ evaluated_options.tap do |options|
360
+ unless options[:mode] == :single_iv_and_salt
361
+ load_iv_for_attribute(attribute, options)
362
+ end
363
+
364
+ if options[:mode] == :per_attribute_iv_and_salt
365
+ load_salt_for_attribute(attribute, options)
366
+ end
367
+ end
312
368
  end
313
369
 
314
370
  # Evaluates symbol (method reference) or proc (responds to call) options
@@ -324,25 +380,53 @@ module AttrEncrypted
324
380
  end
325
381
  end
326
382
 
327
- def load_iv_for_attribute(attribute, algorithm)
328
- encrypted_attribute_name = self.class.encrypted_attributes[attribute.to_sym][:attribute]
383
+ def load_iv_for_attribute(attribute, options)
384
+ encrypted_attribute_name = options[:attribute]
385
+ encode_iv = options[:encode_iv]
329
386
  iv = send("#{encrypted_attribute_name}_iv")
330
- if(iv == nil)
387
+ if options[:operation] == :encrypting
331
388
  begin
332
- algorithm = algorithm || "aes-256-cbc"
333
- algo = OpenSSL::Cipher::Cipher.new(algorithm)
334
- iv = [algo.random_iv].pack("m")
389
+ iv = generate_iv(options[:algorithm])
390
+ iv = [iv].pack(encode_iv) if encode_iv
335
391
  send("#{encrypted_attribute_name}_iv=", iv)
336
392
  rescue RuntimeError
337
393
  end
338
394
  end
339
- self.class.encrypted_attributes[attribute.to_sym] = self.class.encrypted_attributes[attribute.to_sym].merge(:iv => iv.unpack("m").first) if (iv && !iv.empty?)
395
+ if iv && !iv.empty?
396
+ iv = iv.unpack(encode_iv).first if encode_iv
397
+ options[:iv] = iv
398
+ end
399
+ end
400
+
401
+ def generate_iv(algorithm)
402
+ algo = OpenSSL::Cipher.new(algorithm)
403
+ algo.encrypt
404
+ algo.random_iv
405
+ end
406
+
407
+ def load_salt_for_attribute(attribute, options)
408
+ encrypted_attribute_name = options[:attribute]
409
+ encode_salt = options[:encode_salt]
410
+ salt = send("#{encrypted_attribute_name}_salt")
411
+ if (salt == nil)
412
+ salt = SecureRandom.random_bytes
413
+ salt = prefix_and_encode_salt(salt, encode_salt) if encode_salt
414
+ send("#{encrypted_attribute_name}_salt=", salt)
415
+ end
416
+ if salt && !salt.empty?
417
+ salt = decode_salt_if_encoded(salt, encode_salt) if encode_salt
418
+ options[:salt] = salt
419
+ end
420
+ end
421
+
422
+ def prefix_and_encode_salt(salt, encoding)
423
+ prefix = '_'
424
+ prefix + [salt].pack(encoding)
340
425
  end
341
426
 
342
- def load_salt_for_attribute(attribute)
343
- encrypted_attribute_name = self.class.encrypted_attributes[attribute.to_sym][:attribute]
344
- salt = send("#{encrypted_attribute_name}_salt") || send("#{encrypted_attribute_name}_salt=", Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15])
345
- self.class.encrypted_attributes[attribute.to_sym] = self.class.encrypted_attributes[attribute.to_sym].merge(:salt => salt)
427
+ def decode_salt_if_encoded(salt, encoding)
428
+ prefix = '_'
429
+ salt.slice(0).eql?(prefix) ? salt.slice(1..-1).unpack(encoding).first : salt
346
430
  end
347
431
  end
348
432
 
@@ -354,6 +438,5 @@ module AttrEncrypted
354
438
 
355
439
  end
356
440
 
357
- Object.extend AttrEncrypted
358
441
 
359
442
  Dir[File.join(File.dirname(__FILE__), 'attr_encrypted', 'adapters', '*.rb')].each { |adapter| require adapter }