attr_encrypted 1.3.3 → 3.0.1
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 +7 -0
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +6 -0
- data/.travis.yml +24 -0
- data/CHANGELOG.md +79 -0
- data/Gemfile +3 -0
- data/README.md +444 -0
- data/Rakefile +4 -15
- data/attr_encrypted.gemspec +63 -0
- data/certs/saghaulor.pem +21 -0
- data/checksum/attr_encrypted-3.0.0.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.0.gem.sha512 +1 -0
- data/lib/attr_encrypted.rb +196 -105
- data/lib/attr_encrypted/adapters/active_record.rb +64 -21
- 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 +3 -3
- data/test/active_record_test.rb +157 -57
- data/test/attr_encrypted_test.rb +117 -55
- data/test/compatibility_test.rb +20 -37
- data/test/data_mapper_test.rb +6 -6
- data/test/legacy_active_record_test.rb +16 -12
- data/test/legacy_attr_encrypted_test.rb +31 -30
- data/test/legacy_compatibility_test.rb +22 -31
- data/test/legacy_data_mapper_test.rb +11 -8
- data/test/legacy_sequel_test.rb +13 -9
- data/test/run.sh +12 -52
- data/test/sequel_test.rb +6 -6
- data/test/test_helper.rb +28 -16
- metadata +121 -70
- metadata.gz.sig +0 -0
- data/README.rdoc +0 -302
data/Rakefile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require 'rake'
|
2
1
|
require 'rake/testtask'
|
3
|
-
require '
|
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
|
-
|
22
|
-
|
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
|
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-----
|
@@ -0,0 +1 @@
|
|
1
|
+
845fc3cb09a19c3ac76192aba443788f92c880744617bca99b16fd31ce843e07
|
@@ -0,0 +1 @@
|
|
1
|
+
81a065442258cc3702aab62c7b2307a48ed3e0deb803600d11a7480cce0db7c43fd9929acd2755081042f8989236553fd694b6cb62776bbfc53f9165a22cbca1
|
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
|
-
#
|
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
|
-
# :
|
61
|
+
# default_encoding: Defaults to 'm' (base64).
|
47
62
|
#
|
48
|
-
# :
|
49
|
-
#
|
50
|
-
#
|
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
|
-
# :
|
68
|
+
# marshaler: The object to use for marshaling.
|
69
|
+
# Defaults to Marshal.
|
53
70
|
#
|
54
|
-
# :
|
71
|
+
# dump_method: The dump method name to call on the <tt>:marshaler</tt> object to.
|
72
|
+
# Defaults to 'dump'.
|
55
73
|
#
|
56
|
-
# :
|
74
|
+
# load_method: The load method name to call on the <tt>:marshaler</tt> object.
|
75
|
+
# Defaults to 'load'.
|
57
76
|
#
|
58
|
-
# :
|
77
|
+
# encryptor: The object to use for encrypting.
|
78
|
+
# Defaults to Encryptor.
|
59
79
|
#
|
60
|
-
# :
|
80
|
+
# encrypt_method: The encrypt method name to call on the <tt>:encryptor</tt> object.
|
81
|
+
# Defaults to 'encrypt'.
|
61
82
|
#
|
62
|
-
# :
|
83
|
+
# decrypt_method: The decrypt method name to call on the <tt>:encryptor</tt> object.
|
84
|
+
# Defaults to 'decrypt'.
|
63
85
|
#
|
64
|
-
# :
|
65
|
-
#
|
66
|
-
#
|
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
|
-
# :
|
69
|
-
#
|
70
|
-
#
|
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
|
-
# :
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
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
|
-
instance_methods_as_symbols =
|
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,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(:
|
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!(:
|
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!(:
|
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, :
|
273
|
+
# attr_encrypted :email, key: 'my secret key'
|
237
274
|
# end
|
238
275
|
#
|
239
|
-
# User.encrypted_attributes # { :
|
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, :
|
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, :
|
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, :
|
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
|
-
|
306
|
-
|
307
|
-
|
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
|
-
|
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,
|
327
|
-
encrypted_attribute_name =
|
328
|
-
|
329
|
-
|
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
|
-
|
332
|
-
|
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
|
-
|
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
|
342
|
-
|
343
|
-
salt
|
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 }
|