yattr_encrypted 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rake'
4
+ gem 'pry'
5
+ gem 'minitest'
6
+ gem 'activerecord'
7
+ gem 'mocha'
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Sean Huber - shuber@huberry.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.mdown ADDED
@@ -0,0 +1,149 @@
1
+ # YattrEncrypted #
2
+
3
+ Version: 0.1.0 (but you should check lib/yattr_encrypted/version.rb to be sure)
4
+
5
+ ## Applicability ##
6
+
7
+ This code has been tested
8
+
9
+ * using ruby 1.9.2-p290
10
+ * stand alone
11
+ * Rails 3.1.4
12
+
13
+ ## Description ##
14
+
15
+ This is based on code stolen from *yattr_encrypted* and *encryptor* - both by
16
+ github.com/shuber - and both very fine gems.
17
+
18
+ So why another dumb attribute encryptor gem?
19
+
20
+ shuber's gems don't do what I want and they appear to be dormant.
21
+
22
+ The primary difference between **shuber**'s gems and this one is a matter of
23
+ flexibility and simplicity. **yattr_encrypted** is simple, easy to use and
24
+ (should be) pretty secure. This comes at the expense of *flexibility*.
25
+
26
+ In **yattr_encrypted** you do not have a choice of algorithm, encoding, additional
27
+ encrypt/decrypt methods, conditional encryption, or underlying data mapper.
28
+ You also have to use Rails 3.1+.
29
+
30
+ In more detail, here is where they differ:
31
+
32
+ **yattr_encrypted** does not support:
33
+
34
+ * DataMapper
35
+ * Sequel
36
+ * ActiveRecord find_by*** methods for encrypted fields
37
+ * alternate encryption methods
38
+ * alternate encryption algorithms
39
+ * String#encrypt, #encrypt!, #decrypt, and #decrypt!
40
+ * most of the options **yattr_encrypted** supports
41
+ * conditional encrypting - **yattr_encrypted** supports conditionally encrypting
42
+ fields based on some logic. I don't have a use case for this, so **yattr_encrypted**
43
+ does not support it.
44
+
45
+ **yattr_encrypted** is also self contained - only relies on the *openssl* (part
46
+ of the Ruby Standard Library), whereas the *shuber* gem depends on *encryptor*.
47
+
48
+ What **yattr_encrypted** *does* support:
49
+
50
+ * **yattr_encrypted** ONLY works with ActiveRecord
51
+ * random initial values for each encrypted attribute. This is done by creating a
52
+ random *iv* and including it in the encrypted data. See *openssl* documentation
53
+ for details [OpenSSL::Cipher]
54
+ * detects when fields are modified by actions other than assignment. This supports
55
+ encrypting complex types - such as hashes and arrays. This is implemented by adding
56
+ *save* and *save!* methods to models
57
+ * Rails 3.1 & Rails 3.2 - doesn't pretend to support anything lower (but it might work)
58
+
59
+
60
+ ## Installation ##
61
+
62
+ Either
63
+
64
+ gem install yattr_encrypted
65
+
66
+ Or add to your Gemfile
67
+
68
+ gem yattr_encrypted
69
+
70
+ ## Usage ##
71
+
72
+ Name each field you want to encrypt in the `yattr_encrypted` macro.
73
+
74
+ For example, assume that you want to encrypt a field named `foo`. Then create
75
+ a field in your *migration* named `foo_encrypted`.
76
+
77
+ class Foo < ActiveRecord::Base
78
+ yattr_encrypted :foo
79
+ end
80
+
81
+ This will add accessor methods `foo` and `foo=` to your model. You do not use the
82
+ actual `foo_encrypted` field directly.
83
+
84
+ NOTE: `foo` is not useable for search. If you want to search on encrypted fields,
85
+ use **attr\_encrypted**. **yattr\_encrypted** does not create searchable fields because
86
+ it automatically generates random initial values so it is not possible to generate
87
+ matching encrypted values without retrieving the encrypted data.
88
+
89
+ ### Options ###
90
+
91
+ The encrypted field name defaults to `<field>_encrypted`. You can change this on
92
+ a field by field basis using the `:prefix` and `:suffix` - which define strings
93
+ which are prefixed and suffixed to the field name to create the encrypted field name.
94
+ They default to:
95
+
96
+ :prefix = ''
97
+ :suffix = '_encrypted'
98
+
99
+ Notice that the underscore ('_') must be included in `:prefix` and `:suffix`, if you
100
+ want them.
101
+
102
+ ### Encryption keys
103
+
104
+ The default encryption key is the value of `<application>::Application.config.secret_token`
105
+ which is in `config/initializers/secret_token.rb`.
106
+
107
+ If you want to use some other key - on a field by field basis,
108
+ you can specify the key on a field by field basis using the `:key` option.
109
+
110
+ NOTE: all encryption uses `ase-256-cbc` with random initial values. For some reason this
111
+ triggers a key length check in **openssl** which raises an exception if your key is
112
+ too short. I don't know what the required key length is, but `secrete_token` is long enough
113
+ and 'this is a very long secret key' is not.
114
+
115
+ If you supply your own key, it can be a String or a Proc which returns a String.
116
+
117
+ ### Initial Values ###
118
+
119
+ As stated everywhere - random initial values are automatically generated for all fields.
120
+ They are prepended to the actual encrypted data and stripped during decryption. You can't
121
+ override this, nor can you provide your own initial values.
122
+
123
+ ### Encryption Method ###
124
+
125
+ **yattr\_encrypted** only uses `aes-256-cbc`. If you want variety, use **attr\_encrypted**, which
126
+ supports the entire gamete supplied by **openssl**
127
+
128
+ ### Encoding of Encrypted Data and Database Compatibility ###
129
+
130
+ All data saved in the database is `base64` encode. All fields are further
131
+ serialized as JSON objects. This is to avoid dealing with any database idiodicy
132
+ and to transparently handle complex data types being used in database fields [such as
133
+ Hashes and Arrays].
134
+
135
+ The encoding algorithm for a field is:
136
+
137
+ field_jsonified = ActiveSupprt::JSON.encode field
138
+ iv = lambda { (0..16).map { |x| rand(256).chr }.join() }.call
139
+ encrypted_data = YattrEncrypted.encrypt field_jsonified, iv
140
+ field_encrypted = ActiveSupport::Base64.encode64 ("%04d" % iv.length) + iv + encrypted_data
141
+
142
+ The decoding algorithm is:
143
+
144
+ field_decoded = ActiveSupport::Base64::decode64 field_encrypted
145
+ len = field_decoded[0..3].to_i + 1
146
+ iv = field_decoded[4..(len)]
147
+ encrypted_data = field_decoded[(len+1)..-1]
148
+ field_jsonified = YattrEncrypted.decrypt encrypted_data, iv
149
+ field = ActiveSupprt::JSON.decode field_jsonified
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'rake'
2
+ # require 'rake/testtask'
3
+ # require 'rdoc/task'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the yattr_encrypted gem.'
9
+ task :test do
10
+ if ENV['TEST']
11
+ system "ruby #{ENV['TEST']}"
12
+ else
13
+ system "ruby test/*_test.rb"
14
+ end
15
+ end
16
+ # Rake::TestTask.new(:test) do |t|
17
+ # t.libs << 'lib'
18
+ # # t.pattern = 'test/**/*_test.rb'
19
+ # t.pattern = ENV['TEST'] ? ENV['TEST'] : 'test/**/*_test.rb'
20
+ # t.verbose = true
21
+ # end
22
+
23
+ desc 'Build Gem'
24
+ task 'gem' do
25
+ system 'gem build yattr_encrypted.gemspec'
26
+ end
27
+
28
+ desc 'Generate documentation for the yattr_encrypted gem.'
29
+ task 'rdoc' do
30
+ system 'rdoc README.rdoc lib/'
31
+ end
32
+ # RDoc::Task.new do |rdoc|
33
+ # rdoc.rdoc_dir = 'rdoc'
34
+ # rdoc.title = 'yattr_encrypted'
35
+ # rdoc.options << '--line-numbers' << '--inline-source'
36
+ # rdoc.rdoc_files.include('README*')
37
+ # rdoc.rdoc_files.include('lib/**/*.rb')
38
+ # end
@@ -0,0 +1,11 @@
1
+ module YattrEncrypted
2
+ class Railtie < Rails::Railtie
3
+ initializer "active_record.initialize_yattr_encrypted" do
4
+ ActiveSupport.on_load(:active_record) do
5
+ class ActiveRecord::Base
6
+ include YattrEncrypted
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module YattrEncrypted
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,261 @@
1
+ require 'openssl'
2
+ require 'active_support'
3
+ require 'base64'
4
+ require 'yattr_encrypted/railtie' if defined?(Rails)
5
+ require 'yattr_encrypted/version'
6
+
7
+ # how to use
8
+ #
9
+ # class Foo < ActiveRecord::Base
10
+ # yattr_encrypted :foo, :bar
11
+ # end
12
+
13
+ # Adds attr_accessors that encrypt and decrypt an object's attributes
14
+ module YattrEncrypted
15
+
16
+ ALGORITHM = 'aes-256-cbc'
17
+
18
+ # Generates attr_accessors that encrypt and decrypt attributes transparently
19
+ #
20
+ # Options (any other options you specify are passed to the
21
+ # encryptor's encrypt and decrypt methods)
22
+ #
23
+ # :prefix A prefix used to generate the name of the referenced
24
+ # encrypted attributes. For example <tt>attr_accessor
25
+ # :email, :password, :prefix => 'crypted_'</tt> would
26
+ # generate attributes named 'crypted_email' and
27
+ # 'crypted_password' to store the encrypted email and
28
+ # password. Defaults to ''.
29
+ #
30
+ # :suffix A suffix used to generate the name of the referenced
31
+ # encrypted attributes. For example <tt>attr_accessor
32
+ # :email, :password, :prefix => '', :suffix =>
33
+ # '_encrypted'</tt> would generate attributes named
34
+ # 'email_encrypted' and 'password_encrypted' to store the
35
+ # encrypted email. Defaults to '_encrypted'.
36
+ #
37
+ # :key The encryption key. Not generally required.
38
+ # Defaults to Rails.application.config.secret_token
39
+ #
40
+ # Example
41
+ #
42
+ # class User
43
+ # yattr_encrypted :email
44
+ # end
45
+ #
46
+ # @user = User.new
47
+ # @user.encrypted_email # nil? is true
48
+ # @user.email? # false
49
+ # @user.email = 'test@example.com'
50
+ # @user.email? # true
51
+ # @user.encrypted_email # returns the encrypted version of 'test@example.com'
52
+ #
53
+
54
+ def self.included(base)
55
+ class << base
56
+ attr_accessor :yate_encrypted_attributes
57
+
58
+ def yattr_encrypted(*attributes)
59
+ # construct options
60
+ options = {
61
+ :prefix => '',
62
+ :suffix => '_encrypted',
63
+ :key => defined?(::Rails) ? ::Rails.application.config.secret_token : nil,
64
+ }
65
+ # merge specific options
66
+ options.merge!(attributes.pop) if Hash === attributes.last
67
+
68
+ # tell self to define instance methods from the database if they have not already been generated
69
+ define_attribute_methods unless attribute_methods_generated?
70
+
71
+ # collect existing instance methods
72
+ instance_methods_as_symbols = instance_methods.map { |method| method.to_sym }
73
+
74
+ # iterate through attributes
75
+ attributes.map { |x| x.to_sym }.each do |attribute|
76
+ encrypted_attribute_name = [options[:prefix], attribute, options[:suffix]].join.to_sym
77
+
78
+ # barf if reader and write doesn't exist for encrypted attribute
79
+ raise ArgumentError.new("No Reader method for encrypted version of #{attribute}: #{encrypted_attribute_name}") \
80
+ unless instance_methods_as_symbols.include?(encrypted_attribute_name)
81
+ raise ArgumentError.new("No Write method for encrypted version of #{attribute}: #{encrypted_attribute_name}") \
82
+ unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
83
+
84
+ tmp =<<-XXX
85
+ puts "defining #{attribute}"
86
+ def #{attribute}
87
+ unless @#{attribute} && !@#{attribute}.empty?
88
+ options = yate_encrypted_attributes[:#{attribute}]
89
+ @#{attribute} = #{encrypted_attribute_name} ? \
90
+ yate_decrypt(#{encrypted_attribute_name}, options[:key]) : \
91
+ ''
92
+ self.yate_checksums[:#{attribute}] = yate_field_hash_value(:#{attribute})
93
+ self.yate_dirty[:#{attribute}] = true
94
+ end
95
+ @#{attribute}
96
+ end
97
+ XXX
98
+ class_eval(tmp)
99
+
100
+ tmp =<<-XXX
101
+ puts "self: #{self}"
102
+ def #{attribute}= value
103
+ @#{attribute} = value
104
+ options = yate_encrypted_attributes[:#{attribute}]
105
+ self.#{encrypted_attribute_name} = yate_encrypt(value, options[:key])
106
+ self.yate_checksums[:#{attribute}] = yate_field_hash_value(:#{attribute})
107
+ self.yate_dirty[:#{attribute}] = true
108
+ end
109
+ XXX
110
+ class_eval(tmp)
111
+
112
+ define_method("#{attribute}?") do
113
+ value = send(attribute)
114
+ value.respond_to?(:empty?) ? !value.empty? : !!value
115
+ end
116
+
117
+ self.yate_encrypted_attributes ||= {}
118
+
119
+ self.yate_encrypted_attributes[attribute.to_sym] = \
120
+ options.merge(:attribute => encrypted_attribute_name)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
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
+ # protected methods - nobody needs to use these outside of the model
165
+ protected
166
+
167
+ def yate_checksums
168
+ @yate_checksums ||= {}
169
+ end
170
+
171
+ def yate_dirty
172
+ @yate_dirty ||= {}
173
+ end
174
+
175
+ def yate_encrypted_attributes
176
+ self.class.yate_encrypted_attributes
177
+ end
178
+
179
+ private
180
+
181
+ # yate_encrypt(value, key)
182
+ #
183
+ def yate_encrypt(value, key)
184
+ cipher = OpenSSL::Cipher.new(ALGORITHM)
185
+ cipher.encrypt
186
+ cipher.key = key
187
+ iv = cipher.random_iv
188
+
189
+ # jsonify data
190
+ value_marshalled = Marshal.dump value
191
+
192
+ # encrypt data
193
+ result = cipher.update value_marshalled
194
+ result << cipher.final
195
+
196
+ # return encrypted data and iv
197
+ Base64.encode64(("%04d" % iv.length) + iv + result)
198
+ end
199
+
200
+ # yate_decrypt(encrypted_value, key)
201
+ def yate_decrypt(marshalled_value, key)
202
+ # initialize decryptor
203
+ cipher = OpenSSL::Cipher.new(ALGORITHM)
204
+ cipher.decrypt
205
+ cipher.key = key
206
+
207
+ # extract encrypted_value
208
+ encrypted_value = Base64.decode64 marshalled_value
209
+
210
+ # extract and set iv
211
+ iv_end = encrypted_value[0..3].to_i + 3
212
+ cipher.iv = encrypted_value[4..(iv_end)]
213
+ encrypted_value = encrypted_value[(iv_end+1)..-1]
214
+
215
+ # derypte and return
216
+ result = cipher.update(encrypted_value)
217
+ result << cipher.final
218
+
219
+ Marshal.load result
220
+ end
221
+
222
+ # support for fields which are not atomic values
223
+ def yate_field_hash_value(attribute)
224
+ attribute = attribute.to_s if Symbol === attribute
225
+ OpenSSL::HMAC.digest('md5', 'ersatz key', Marshal.dump(self.instance_variable_get("@#{attribute}")))
226
+ end
227
+
228
+ def yate_field_changed?(attribute)
229
+ attribute = attribute.to_sym unless Symbol === attribute
230
+ yate_field_hash_value(attribute) != self.yate_checksums[attribute] || self.yate_dirty[attribute]
231
+ end
232
+
233
+ def yate_update_encrypted_values
234
+ yate_encrypted_attributes.each do |attribute, options|
235
+ if yate_field_changed?(attribute)
236
+ self.send "#{options[:attribute]}=".to_sym, yate_encrypt(self.send(attribute), options[:key])
237
+ yate_dirty.delete(attribute)
238
+ end
239
+ end
240
+ end
241
+
242
+ # protected
243
+ #
244
+ # # Returns yattr_encrypted options evaluated in the current object's scope for the attribute specified
245
+ # def yate_evaluated_options_for(attribute)
246
+ # self.class.encrypted_attributes[attribute.to_sym].inject({}) do |hash, (option, value)|
247
+ # hash.merge!(option => yate_evaluate_option(value))
248
+ # end
249
+ # end
250
+ #
251
+ # # Evaluates symbol (method reference) or proc (responds to call) options
252
+ # #
253
+ # # If the option is not a proc then the original option is returned
254
+ # def yate_evaluate_option(option)
255
+ # if option.respond_to?(:call)
256
+ # option.call(self)
257
+ # else
258
+ # option
259
+ # end
260
+ # end
261
+ end
@@ -0,0 +1,8 @@
1
+ require 'pry'
2
+ require 'minitest/autorun'
3
+ require 'digest/sha2'
4
+ require 'rubygems'
5
+
6
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $:.unshift(File.dirname(__FILE__))
8
+ require 'yattr_encrypted'
@@ -0,0 +1,66 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ # require 'active_record'
4
+ require 'yattr_encrypted'
5
+
6
+ module ActiveRecord
7
+ class Base
8
+ include YattrEncrypted
9
+
10
+ def self.attribute_methods_generated?
11
+ true
12
+ end
13
+
14
+ def save
15
+ true
16
+ end
17
+
18
+ def save!
19
+ true
20
+ end
21
+
22
+ def update_attribute attribute, value
23
+ true
24
+ end
25
+
26
+ def update_attributes attribute_hash, options
27
+ true
28
+ end
29
+ end
30
+ end
31
+
32
+ class SomeClass < ActiveRecord::Base
33
+ attr_accessor :field_encrypted
34
+ yattr_encrypted :field, :key => 'a honkin big key: honk honk honk honk honk'
35
+ end
36
+
37
+ class TestYattrEncrypted < MiniTest::Unit::TestCase
38
+ def setup
39
+ @sc = SomeClass.new
40
+ end
41
+
42
+ def test_yattr_encrypted_should_create_accessors
43
+ assert @sc.respond_to?(:field), "a SomeClass instance should respond to :field"
44
+ assert @sc.respond_to?(:field=), "a SomeClass instance should respond to :field="
45
+ assert @sc.respond_to?(:field?), "a SomeClass instance should respond to :field?"
46
+ assert @sc.respond_to?(:yate_encrypted_attributes), "a SomeClass instance should respond to :yate_encrypted_attributes"
47
+ end
48
+
49
+ def test_assigning_field_should_assign_field_encrypted
50
+ assert_nil @sc.field_encrypted, "field_encrypted should be nil prior to assignment to field"
51
+ @sc.field = 'a field value'
52
+ refute_nil @sc.field_encrypted, "field_encrypted should not be nil"
53
+ assert_equal 'a field value', @sc.field, "@sc.field should match input"
54
+ options = @sc.send(:yate_encrypted_attributes)[:field]
55
+ assert_equal 'a field value', @sc.send(:yate_decrypt, @sc.field_encrypted, options[:key]), \
56
+ "decrypting @sc.field_encrypted should match input"
57
+ end
58
+
59
+ def test_save_should_update_encrypted
60
+ @sc.field = { key: 'value' }
61
+ @sc.save
62
+ options = @sc.send(:yate_encrypted_attributes)[:field]
63
+ decrypted = @sc.send(:yate_decrypt, @sc.field_encrypted, options[:key])
64
+ assert_equal( { key: 'value' }, decrypted, "decrypt @sc.field_encrypted should be correct")
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yattr_encrypted
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mike Howard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-18 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pry
16
+ requirement: &2151816860 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2151816860
25
+ description: Generates yattr_accessors that encrypt and decrypt attributes transparently.
26
+ Based on attr_encrypted by Sean Huber [https://github.com/shuber]
27
+ email: mike@clove.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/yattr_encrypted/railtie.rb
33
+ - lib/yattr_encrypted/version.rb
34
+ - lib/yattr_encrypted.rb
35
+ - MIT-LICENSE
36
+ - Rakefile
37
+ - README.mdown
38
+ - Gemfile
39
+ - test/test_helper.rb
40
+ - test/yattr_encrypted_test.rb
41
+ homepage: http://github.mikhoward/yattr_encrypted
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --line-numbers
46
+ - --inline-source
47
+ - --main
48
+ - README.mdown
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ segments:
58
+ - 0
59
+ hash: -3522761952983170560
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 1.8.6
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Encrypt and decrypt attributes
72
+ test_files:
73
+ - test/test_helper.rb
74
+ - test/yattr_encrypted_test.rb