attr_encrypted 1.3.3 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
- require 'rake'
2
1
  require 'rake/testtask'
3
- require 'rake/rdoctask'
2
+ require 'rdoc/task'
3
+ require "bundler/gem_tasks"
4
4
 
5
5
  desc 'Test the attr_encrypted gem.'
6
6
  Rake::TestTask.new(:test) do |t|
@@ -18,16 +18,5 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
18
18
  rdoc.rdoc_files.include('lib/**/*.rb')
19
19
  end
20
20
 
21
- if RUBY_VERSION < '1.9.3'
22
- require 'rcov/rcovtask'
23
-
24
- task :rcov do
25
- sh "rcov -o coverage/rcov --exclude '^(?!lib)' " + FileList[ 'test/**/*_test.rb' ].join(' ')
26
- end
27
-
28
- desc 'Default: run unit tests under rcov.'
29
- task :default => :rcov
30
- else
31
- desc 'Default: run unit tests.'
32
- task :default => :test
33
- end
21
+ desc 'Default: run unit tests.'
22
+ task :default => :test
@@ -0,0 +1,63 @@
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', ['~> 3.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 were deprecated in attr_encrypted v2.0.0.\n
59
+ Additionally, there was a bug in Encryptor v2.0.0 that insecurely encrypted data when using an AES-*-GCM algorithm.\n
60
+ This bug was fixed but introduced breaking changes between v2.x and v3.x.\n
61
+ Please see the README for more information regarding upgrading to attr_encrypted v3.0.0.\n\n\n"
62
+
63
+ 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-----
@@ -0,0 +1 @@
1
+ 845fc3cb09a19c3ac76192aba443788f92c880744617bca99b16fd31ce843e07
@@ -0,0 +1 @@
1
+ 81a065442258cc3702aab62c7b2307a48ed3e0deb803600d11a7480cce0db7c43fd9929acd2755081042f8989236553fd694b6cb62776bbfc53f9165a22cbca1
@@ -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.
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.
45
60
  #
46
- # :default_encoding => Defaults to 'm' (base64).
61
+ # default_encoding: Defaults to 'm' (base64).
47
62
  #
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.
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.
51
67
  #
52
- # :marshaler => The object to use for marshaling. Defaults to Marshal.
68
+ # marshaler: The object to use for marshaling.
69
+ # Defaults to Marshal.
53
70
  #
54
- # :dump_method => The dump method name to call on the <tt>:marshaler</tt> object to. Defaults to 'dump'.
71
+ # dump_method: The dump method name to call on the <tt>:marshaler</tt> object to.
72
+ # Defaults to 'dump'.
55
73
  #
56
- # :load_method => The load method name to call on the <tt>:marshaler</tt> object. Defaults to 'load'.
74
+ # load_method: The load method name to call on the <tt>:marshaler</tt> object.
75
+ # Defaults to 'load'.
57
76
  #
58
- # :encryptor => The object to use for encrypting. Defaults to Encryptor.
77
+ # encryptor: The object to use for encrypting.
78
+ # Defaults to Encryptor.
59
79
  #
60
- # :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'encrypt'.
80
+ # encrypt_method: The encrypt method name to call on the <tt>:encryptor</tt> object.
81
+ # Defaults to 'encrypt'.
61
82
  #
62
- # :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'decrypt'.
83
+ # decrypt_method: The decrypt method name to call on the <tt>:encryptor</tt> object.
84
+ # Defaults to 'decrypt'.
63
85
  #
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.
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.
67
91
  #
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.
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.
71
97
  #
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>.
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
- instance_methods_as_symbols = instance_methods.collect { |method| method.to_sym }
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,9 +165,10 @@ 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
171
+
159
172
  alias_method :attr_encryptor, :attr_encrypted
160
173
 
161
174
  # Default options to use with calls to <tt>attr_encrypted</tt>
@@ -165,6 +178,30 @@ module AttrEncrypted
165
178
  @attr_encrypted_options ||= superclass.attr_encrypted_options.dup
166
179
  end
167
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
+
168
205
  # Checks if an attribute is configured with <tt>attr_encrypted</tt>
169
206
  #
170
207
  # Example
@@ -193,7 +230,7 @@ module AttrEncrypted
193
230
  options = encrypted_attributes[attribute.to_sym].merge(options)
194
231
  if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
195
232
  encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
196
- 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))
197
234
  if options[:marshal]
198
235
  value = options[:marshaler].send(options[:load_method], value)
199
236
  elsif defined?(Encoding)
@@ -219,7 +256,7 @@ module AttrEncrypted
219
256
  options = encrypted_attributes[attribute.to_sym].merge(options)
220
257
  if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
221
258
  value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
222
- 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))
223
260
  encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
224
261
  encrypted_value
225
262
  else
@@ -233,10 +270,10 @@ module AttrEncrypted
233
270
  # Example
234
271
  #
235
272
  # class User
236
- # attr_encrypted :email, :key => 'my secret key'
273
+ # attr_encrypted :email, key: 'my secret key'
237
274
  # end
238
275
  #
239
- # User.encrypted_attributes # { :email => { :attribute => 'encrypted_email', :key => 'my secret key' } }
276
+ # User.encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
240
277
  def encrypted_attributes
241
278
  @encrypted_attributes ||= superclass.encrypted_attributes.dup
242
279
  end
@@ -247,7 +284,7 @@ module AttrEncrypted
247
284
  # Example
248
285
  #
249
286
  # class User
250
- # attr_encrypted :email, :key => 'my secret key'
287
+ # attr_encrypted :email, key: 'my secret key'
251
288
  # end
252
289
  #
253
290
  # User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
@@ -266,7 +303,7 @@ module AttrEncrypted
266
303
  #
267
304
  # class User
268
305
  # attr_accessor :secret_key
269
- # attr_encrypted :email, :key => :secret_key
306
+ # attr_encrypted :email, key: :secret_key
270
307
  #
271
308
  # def initialize(secret_key)
272
309
  # self.secret_key = secret_key
@@ -276,6 +313,7 @@ module AttrEncrypted
276
313
  # @user = User.new('some-secret-key')
277
314
  # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
278
315
  def decrypt(attribute, encrypted_value)
316
+ encrypted_attributes[attribute.to_sym][:operation] = :decrypting
279
317
  self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
280
318
  end
281
319
 
@@ -285,7 +323,7 @@ module AttrEncrypted
285
323
  #
286
324
  # class User
287
325
  # attr_accessor :secret_key
288
- # attr_encrypted :email, :key => :secret_key
326
+ # attr_encrypted :email, key: :secret_key
289
327
  #
290
328
  # def initialize(secret_key)
291
329
  # self.secret_key = secret_key
@@ -295,19 +333,38 @@ module AttrEncrypted
295
333
  # @user = User.new('some-secret-key')
296
334
  # @user.encrypt(:email, 'test@example.com')
297
335
  def encrypt(attribute, value)
336
+ encrypted_attributes[attribute.to_sym][:operation] = :encrypting
298
337
  self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
299
338
  end
300
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
+
301
347
  protected
302
348
 
303
349
  # Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
304
350
  def evaluated_attr_encrypted_options_for(attribute)
305
- if self.class.encrypted_attributes[attribute.to_sym][:mode] == :per_attribute_iv_and_salt
306
- load_iv_for_attribute(attribute, self.class.encrypted_attributes[attribute.to_sym][:algorithm])
307
- load_salt_for_attribute(attribute)
351
+ evaluated_options = Hash.new
352
+ attribute_option_value = encrypted_attributes[attribute.to_sym][:attribute]
353
+ encrypted_attributes[attribute.to_sym].map do |option, value|
354
+ evaluated_options[option] = evaluate_attr_encrypted_option(value)
308
355
  end
309
356
 
310
- 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
311
368
  end
312
369
 
313
370
  # Evaluates symbol (method reference) or proc (responds to call) options
@@ -323,29 +380,63 @@ module AttrEncrypted
323
380
  end
324
381
  end
325
382
 
326
- def load_iv_for_attribute(attribute, algorithm)
327
- encrypted_attribute_name = self.class.encrypted_attributes[attribute.to_sym][:attribute]
328
- iv = send("#{encrypted_attribute_name}_iv")
329
- if(iv == nil)
383
+ def load_iv_for_attribute(attribute, options)
384
+ encrypted_attribute_name = options[:attribute]
385
+ encode_iv = options[:encode_iv]
386
+ iv = options[:iv] || send("#{encrypted_attribute_name}_iv")
387
+ if options[:operation] == :encrypting
330
388
  begin
331
- algorithm = algorithm || "aes-256-cbc"
332
- algo = OpenSSL::Cipher::Cipher.new(algorithm)
333
- iv = [algo.random_iv].pack("m")
389
+ iv = generate_iv(options[:algorithm])
390
+ iv = [iv].pack(encode_iv) if encode_iv
334
391
  send("#{encrypted_attribute_name}_iv=", iv)
335
392
  rescue RuntimeError
336
393
  end
337
394
  end
338
- 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 = options[: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)
339
425
  end
340
426
 
341
- def load_salt_for_attribute(attribute)
342
- encrypted_attribute_name = self.class.encrypted_attributes[attribute.to_sym][:attribute]
343
- salt = send("#{encrypted_attribute_name}_salt") || send("#{encrypted_attribute_name}_salt=", Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15])
344
- 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
345
430
  end
346
431
  end
432
+
433
+ protected
434
+
435
+ def attribute_instance_methods_as_symbols
436
+ instance_methods.collect { |method| method.to_sym }
437
+ end
438
+
347
439
  end
348
440
 
349
- Object.extend AttrEncrypted
350
441
 
351
442
  Dir[File.join(File.dirname(__FILE__), 'attr_encrypted', 'adapters', '*.rb')].each { |adapter| require adapter }