yattr_encrypted 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.mdown +37 -15
- data/lib/yattr_encrypted/version.rb +1 -1
- data/lib/yattr_encrypted.rb +12 -48
- data/test/yattr_encrypted_test.rb +11 -1
- metadata +5 -5
data/README.mdown
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# YattrEncrypted #
|
2
2
|
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.3 (but you should check lib/yattr_encrypted/version.rb to be sure)
|
4
4
|
|
5
5
|
## Applicability ##
|
6
6
|
|
@@ -53,7 +53,7 @@ random *iv* and including it in the encrypted data. See *openssl* documentation
|
|
53
53
|
for details [OpenSSL::Cipher]
|
54
54
|
* detects when fields are modified by actions other than assignment. This supports
|
55
55
|
encrypting complex types - such as hashes and arrays. This is implemented by adding
|
56
|
-
|
56
|
+
a `before_save` calleback to the private method *yattr_update_encrypted_values*
|
57
57
|
* Rails 3.1 & Rails 3.2 - doesn't pretend to support anything lower (but it might work)
|
58
58
|
|
59
59
|
|
@@ -132,18 +132,40 @@ serialized as JSON objects. This is to avoid dealing with any database idiodicy
|
|
132
132
|
and to transparently handle complex data types being used in database fields [such as
|
133
133
|
Hashes and Arrays].
|
134
134
|
|
135
|
-
The encoding
|
135
|
+
The encoding code for a field is:
|
136
136
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
137
|
+
cipher = OpenSSL::Cipher.new(ALGORITHM)
|
138
|
+
cipher.encrypt
|
139
|
+
cipher.key = key
|
140
|
+
iv = cipher.random_iv # ask OpenSSL for a new, random initial value
|
141
141
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
142
|
+
# jsonify data
|
143
|
+
value_marshalled = Marshal.dump value
|
144
|
+
|
145
|
+
# encrypt data
|
146
|
+
result = cipher.update value_marshalled
|
147
|
+
result << cipher.final
|
148
|
+
|
149
|
+
# return encrypted data and iv
|
150
|
+
Base64.encode64(("%04d" % iv.length) + iv + result)
|
151
|
+
|
152
|
+
The decoding code is:
|
153
|
+
|
154
|
+
# initialize decryptor
|
155
|
+
cipher = OpenSSL::Cipher.new(ALGORITHM)
|
156
|
+
cipher.decrypt
|
157
|
+
cipher.key = key
|
158
|
+
|
159
|
+
# extract encrypted_value
|
160
|
+
encrypted_value = Base64.decode64 marshalled_value
|
161
|
+
|
162
|
+
# extract and set iv
|
163
|
+
iv_end = encrypted_value[0..3].to_i + 3
|
164
|
+
cipher.iv = encrypted_value[4..(iv_end)]
|
165
|
+
encrypted_value = encrypted_value[(iv_end+1)..-1]
|
166
|
+
|
167
|
+
# derypte and return
|
168
|
+
result = cipher.update(encrypted_value)
|
169
|
+
result << cipher.final
|
170
|
+
|
171
|
+
Marshal.load result
|
data/lib/yattr_encrypted.rb
CHANGED
@@ -67,11 +67,14 @@ module YattrEncrypted
|
|
67
67
|
|
68
68
|
# tell self to define instance methods from the database if they have not already been generated
|
69
69
|
define_attribute_methods unless attribute_methods_generated?
|
70
|
+
|
71
|
+
# schedule yattr_update_encrypted_values before save
|
72
|
+
self.send :before_save, :yate_update_encrypted_values unless self.yate_encrypted_attributes
|
70
73
|
|
71
74
|
# collect existing instance methods
|
72
75
|
instance_methods_as_symbols = instance_methods.map { |method| method.to_sym }
|
73
76
|
|
74
|
-
# iterate through attributes
|
77
|
+
# iterate through attributes and create accessors, verify encryped accessors exist
|
75
78
|
attributes.map { |x| x.to_sym }.each do |attribute|
|
76
79
|
encrypted_attribute_name = [options[:prefix], attribute, options[:suffix]].join.to_sym
|
77
80
|
|
@@ -82,14 +85,13 @@ module YattrEncrypted
|
|
82
85
|
unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
|
83
86
|
|
84
87
|
tmp =<<-XXX
|
85
|
-
puts "defining #{attribute}"
|
86
88
|
def #{attribute}
|
87
89
|
unless @#{attribute} && !@#{attribute}.empty?
|
88
90
|
options = yate_encrypted_attributes[:#{attribute}]
|
89
91
|
@#{attribute} = #{encrypted_attribute_name} ? \
|
90
92
|
yate_decrypt(#{encrypted_attribute_name}, options[:key]) : \
|
91
93
|
''
|
92
|
-
self.yate_checksums[:#{attribute}] =
|
94
|
+
self.yate_checksums[:#{attribute}] = yate_attribute_hash_value(:#{attribute})
|
93
95
|
self.yate_dirty[:#{attribute}] = true
|
94
96
|
end
|
95
97
|
@#{attribute}
|
@@ -98,12 +100,11 @@ module YattrEncrypted
|
|
98
100
|
class_eval(tmp)
|
99
101
|
|
100
102
|
tmp =<<-XXX
|
101
|
-
puts "self: #{self}"
|
102
103
|
def #{attribute}= value
|
103
104
|
@#{attribute} = value
|
104
105
|
options = yate_encrypted_attributes[:#{attribute}]
|
105
106
|
self.#{encrypted_attribute_name} = yate_encrypt(value, options[:key])
|
106
|
-
self.yate_checksums[:#{attribute}] =
|
107
|
+
self.yate_checksums[:#{attribute}] = yate_attribute_hash_value(:#{attribute})
|
107
108
|
self.yate_dirty[:#{attribute}] = true
|
108
109
|
end
|
109
110
|
XXX
|
@@ -123,44 +124,6 @@ module YattrEncrypted
|
|
123
124
|
end
|
124
125
|
end
|
125
126
|
|
126
|
-
# Checks if an attribute is configured with <tt>yattr_encrypted</tt>
|
127
|
-
def yattr_encrypted?(attribute)
|
128
|
-
self.class.yate_encrypted_attributes.has_key?(attribute.to_sym)
|
129
|
-
end
|
130
|
-
|
131
|
-
def save *args
|
132
|
-
yate_update_encrypted_values
|
133
|
-
super
|
134
|
-
end
|
135
|
-
|
136
|
-
def save! *args
|
137
|
-
yate_update_encrypted_values
|
138
|
-
super
|
139
|
-
end
|
140
|
-
|
141
|
-
def update_attribute attribute, value
|
142
|
-
if (options = yate_encrypted_attributes[attribute])
|
143
|
-
self.send "#{attribute}=".to_sym, value
|
144
|
-
update_attribute options[:attribute], self.send(options[:attribute]) if yate_field_changed? attribute
|
145
|
-
else
|
146
|
-
super
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def update_attributes params, options = {}
|
151
|
-
tmp = {}
|
152
|
-
params.keys.each do |attribute|
|
153
|
-
if (options = yate_encrypted_attributes[attribute])
|
154
|
-
self.send "#{attribute}=", params[attribute]
|
155
|
-
tmp[options[:attribute]] = self.send options[:attribute]
|
156
|
-
else
|
157
|
-
tmp[attribute] = params[attribute]
|
158
|
-
end
|
159
|
-
end
|
160
|
-
params = tmp
|
161
|
-
super
|
162
|
-
end
|
163
|
-
|
164
127
|
# protected methods - nobody needs to use these outside of the model
|
165
128
|
protected
|
166
129
|
|
@@ -184,7 +147,7 @@ module YattrEncrypted
|
|
184
147
|
cipher = OpenSSL::Cipher.new(ALGORITHM)
|
185
148
|
cipher.encrypt
|
186
149
|
cipher.key = key
|
187
|
-
iv = cipher.random_iv
|
150
|
+
iv = cipher.random_iv # ask OpenSSL for a new, random initial value
|
188
151
|
|
189
152
|
# jsonify data
|
190
153
|
value_marshalled = Marshal.dump value
|
@@ -220,19 +183,20 @@ module YattrEncrypted
|
|
220
183
|
end
|
221
184
|
|
222
185
|
# support for fields which are not atomic values
|
223
|
-
def
|
186
|
+
def yate_attribute_hash_value(attribute)
|
224
187
|
attribute = attribute.to_s if Symbol === attribute
|
225
188
|
OpenSSL::HMAC.digest('md5', 'ersatz key', Marshal.dump(self.instance_variable_get("@#{attribute}")))
|
226
189
|
end
|
227
190
|
|
228
|
-
def
|
191
|
+
def yate_attribute_changed?(attribute)
|
229
192
|
attribute = attribute.to_sym unless Symbol === attribute
|
230
|
-
|
193
|
+
yate_attribute_hash_value(attribute) != self.yate_checksums[attribute] || self.yate_dirty[attribute]
|
231
194
|
end
|
232
195
|
|
233
196
|
def yate_update_encrypted_values
|
197
|
+
return unless yate_encrypted_attributes
|
234
198
|
yate_encrypted_attributes.each do |attribute, options|
|
235
|
-
if
|
199
|
+
if yate_attribute_changed?(attribute)
|
236
200
|
self.send "#{options[:attribute]}=".to_sym, yate_encrypt(self.send(attribute), options[:key])
|
237
201
|
yate_dirty.delete(attribute)
|
238
202
|
end
|
@@ -10,6 +10,11 @@ module ActiveRecord
|
|
10
10
|
def self.attribute_methods_generated?
|
11
11
|
true
|
12
12
|
end
|
13
|
+
|
14
|
+
def self.before_save *methods
|
15
|
+
@before_save_hooks ||= []
|
16
|
+
@before_save_hooks += methods
|
17
|
+
end
|
13
18
|
|
14
19
|
def save
|
15
20
|
true
|
@@ -39,6 +44,11 @@ class TestYattrEncrypted < MiniTest::Unit::TestCase
|
|
39
44
|
@sc = SomeClass.new
|
40
45
|
end
|
41
46
|
|
47
|
+
def test_before_save_hook
|
48
|
+
assert SomeClass.instance_variable_get(:@before_save_hooks).include?(:yate_update_encrypted_values), \
|
49
|
+
"before_save_hooks should include :yate_encrypted_attributes"
|
50
|
+
end
|
51
|
+
|
42
52
|
def test_yattr_encrypted_should_create_accessors
|
43
53
|
assert @sc.respond_to?(:field), "a SomeClass instance should respond to :field"
|
44
54
|
assert @sc.respond_to?(:field=), "a SomeClass instance should respond to :field="
|
@@ -46,7 +56,7 @@ class TestYattrEncrypted < MiniTest::Unit::TestCase
|
|
46
56
|
assert @sc.respond_to?(:yate_encrypted_attributes), "a SomeClass instance should respond to :yate_encrypted_attributes"
|
47
57
|
end
|
48
58
|
|
49
|
-
def
|
59
|
+
def test_assigning_attribute_should_assign_attribute_encrypted
|
50
60
|
assert_nil @sc.field_encrypted, "field_encrypted should be nil prior to assignment to field"
|
51
61
|
@sc.field = 'a field value'
|
52
62
|
refute_nil @sc.field_encrypted, "field_encrypted should not be nil"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yattr_encrypted
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
12
|
+
date: 2012-03-19 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
16
|
-
requirement: &
|
16
|
+
requirement: &2151767260 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2151767260
|
25
25
|
description: Generates yattr_accessors that encrypt and decrypt attributes transparently.
|
26
26
|
Based on attr_encrypted by Sean Huber [https://github.com/shuber]
|
27
27
|
email: mike@clove.com
|
@@ -56,7 +56,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
56
|
version: '0'
|
57
57
|
segments:
|
58
58
|
- 0
|
59
|
-
hash: -
|
59
|
+
hash: -3636252519896888946
|
60
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|