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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +6 -0
- data/.travis.yml +24 -0
- data/CHANGELOG.md +71 -0
- data/Gemfile +3 -0
- data/README.md +420 -0
- data/Rakefile +3 -15
- data/attr_encrypted.gemspec +60 -0
- data/certs/saghaulor.pem +21 -0
- data/lib/attr_encrypted.rb +197 -114
- data/lib/attr_encrypted/adapters/active_record.rb +8 -8
- data/lib/attr_encrypted/adapters/data_mapper.rb +1 -0
- data/lib/attr_encrypted/adapters/sequel.rb +1 -0
- data/lib/attr_encrypted/version.rb +2 -2
- data/test/active_record_test.rb +40 -18
- data/test/attr_encrypted_test.rb +101 -39
- data/test/compatibility_test.rb +19 -36
- data/test/data_mapper_test.rb +1 -1
- data/test/legacy_active_record_test.rb +11 -7
- data/test/legacy_attr_encrypted_test.rb +17 -16
- data/test/legacy_compatibility_test.rb +21 -30
- data/test/legacy_data_mapper_test.rb +6 -3
- data/test/legacy_sequel_test.rb +8 -4
- data/test/run.sh +12 -52
- data/test/sequel_test.rb +1 -1
- data/test/test_helper.rb +27 -17
- metadata +62 -28
- metadata.gz.sig +2 -0
- data/README.rdoc +0 -344
data/Rakefile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
require 'rake'
|
2
1
|
require 'rake/testtask'
|
3
|
-
require '
|
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
|
-
|
23
|
-
|
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
|
data/certs/saghaulor.pem
ADDED
@@ -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-----
|
data/lib/attr_encrypted.rb
CHANGED
@@ -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
|
18
|
-
#
|
19
|
-
# :
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
# :
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
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
|
-
# #
|
82
|
-
# attr_encrypted_options.merge!(:
|
83
|
-
# attr_encrypted :configuration, :
|
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, :
|
91
|
-
# attr_encrypted :configuration, :
|
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 = { :
|
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
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
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(:
|
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!(:
|
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!(:
|
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, :
|
273
|
+
# attr_encrypted :email, key: 'my secret key'
|
238
274
|
# end
|
239
275
|
#
|
240
|
-
# User.encrypted_attributes # { :
|
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, :
|
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, :
|
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, :
|
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
|
-
|
307
|
-
|
308
|
-
|
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
|
-
|
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,
|
328
|
-
encrypted_attribute_name =
|
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
|
387
|
+
if options[:operation] == :encrypting
|
331
388
|
begin
|
332
|
-
|
333
|
-
|
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
|
-
|
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
|
343
|
-
|
344
|
-
salt
|
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 }
|