attr_encryptor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ pkg
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ rdoc
8
+ spec/reports
9
+ test/tmp
10
+ test/version_tmp
11
+ tmp
12
+
13
+ # YARD artifacts
14
+ .yardoc
15
+ _yardoc
16
+ doc/
data/.rvmrc ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
+ # development environment upon cd'ing into the directory
5
+
6
+ # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
+ environment_id="ruby-1.9.2-p290@attr_encrypted"
8
+
9
+ #
10
+ # Uncomment following line if you want options to be set only for given project.
11
+ #
12
+ # PROJECT_JRUBY_OPTS=( --1.9 )
13
+
14
+ #
15
+ # First we attempt to load the desired environment directly from the environment
16
+ # file. This is very fast and efficient compared to running through the entire
17
+ # CLI and selector. If you want feedback on which environment was used then
18
+ # insert the word 'use' after --create as this triggers verbose mode.
19
+ #
20
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
21
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
22
+ then
23
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
24
+
25
+ if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
26
+ then
27
+ . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
28
+ fi
29
+ else
30
+ # If the environment file has not yet been created, use the RVM CLI to select.
31
+ if ! rvm --create "$environment_id"
32
+ then
33
+ echo "Failed to create RVM environment '${environment_id}'."
34
+ exit 1
35
+ fi
36
+ fi
37
+
38
+ #
39
+ # If you use an RVM gemset file to install a list of gems (*.gems), you can have
40
+ # it be automatically loaded. Uncomment the following and adjust the filename if
41
+ # necessary.
42
+ #
43
+ # filename=".gems"
44
+ # if [[ -s "$filename" ]] ; then
45
+ # rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
46
+ # fi
47
+
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.md ADDED
@@ -0,0 +1,291 @@
1
+ # attr_encryptor
2
+
3
+ Generates attr_accessors that encrypt and decrypt attributes transparently
4
+
5
+ It works with ANY class, however, you get a few extra features when you're using it with `ActiveRecord`, `DataMapper`, or `Sequel`
6
+
7
+
8
+ ## Installation
9
+
10
+ gem install attr_encryptor
11
+
12
+ ## Usage
13
+
14
+ ### Basic
15
+
16
+ Encrypting attributes has never been easier:
17
+
18
+ ### You database
19
+
20
+ add a `encrypted_ssn`, `encrypted_ssn_salt`, `encrypted_ssn_iv`. All of
21
+ them will be populated automatically
22
+
23
+ create_table :google_apps_admins do |t|
24
+ t.string :username
25
+ t.string :encrypted_password
26
+ t.string :encrypted_password_iv
27
+ t.string :domain
28
+ t.timestamps
29
+ end
30
+
31
+ ### Your model
32
+
33
+ class User
34
+ attr_accessor :name
35
+ attr_encrypted :ssn, :key => 'a secret key'
36
+
37
+ end
38
+
39
+ ### Controllers/views
40
+
41
+
42
+ @user = User.new
43
+ @user.ssn = '123-45-6789'
44
+ @user.ssn # returns the unencrypted version of :ssn
45
+ @user.save
46
+
47
+ @user = User.load
48
+ @user.ssn # decrypts :encrypted_ssn and returns '123-45-6789'
49
+
50
+ The `attr_encrypted` method is also aliased as `attr_encryptor` to conform to Ruby's `attr_` naming conventions.
51
+
52
+
53
+ ### Specifying the encrypted attribute name
54
+
55
+ By default, the encrypted attribute name is `encrypted_#{attribute}` (e.g. `attr_encrypted :email` would create an attribute named `encrypted_email`). So, if you're storing the encrypted attribute in the database, you need to make sure the `encrypted_#{attribute}` field exists in your table(as well as `encrypted_#{attribute}_iv` and `encrypted_#{attribute}_salt`). You have a couple of options if you want to name your attribute something else.
56
+
57
+ #### The `:attribute` option
58
+
59
+ You can simply pass the name of the encrypted attribute as the `:attribute` option:
60
+
61
+ class User
62
+ attr_encrypted :email, :key => 'a secret key', :attribute => 'email_encrypted'
63
+ end
64
+
65
+ This would generate an attribute named `email_encrypted`
66
+
67
+ #### The `:prefix` and `:suffix` options
68
+
69
+ If you're planning on encrypting a few different attributes and you don't like the `encrypted_#{attribute}` naming convention then you can specify your own:
70
+
71
+ class User
72
+ attr_encrypted :email, :credit_card, :ssn, :key => 'a secret key', :prefix => 'secret_', :suffix => '_crypted'
73
+ end
74
+
75
+ This would generate the following attributes: `secret_email_crypted`, `secret_credit_card_crypted`, and `secret_ssn_crypted`.
76
+
77
+
78
+ ### Encryption keys
79
+
80
+ Although a `:key` option may not be required (see custom encryptor below), it has a few special features
81
+
82
+ #### Unique keys for each attribute
83
+
84
+ You can specify unique keys for each attribute if you'd like:
85
+
86
+ class User
87
+ attr_encrypted :email, :key => 'a secret key'
88
+ attr_encrypted :ssn, :key => 'a different secret key'
89
+ end
90
+
91
+
92
+ #### Symbols representing instance methods as keys
93
+
94
+ If your class has an instance method that determines the encryption key to use, simply pass a symbol representing it like so:
95
+
96
+ class User
97
+ attr_encrypted :email, :key => :encryption_key
98
+
99
+ def encryption_key
100
+ # does some fancy logic and returns an encryption key
101
+ end
102
+ end
103
+
104
+
105
+ #### Procs as keys
106
+
107
+ You can pass a proc/lambda object as the `:key` option as well:
108
+
109
+ class User
110
+ attr_accessor :key
111
+ attr_encrypted :email, :key => proc { |user| user.key }
112
+ end
113
+
114
+ However when calling `User.new`, `User.create`, `User.update_attributes` the `:key` attribute has to precede the `:email` or key will be nil and you'll get an Exception,
115
+
116
+ ### Conditional encrypting
117
+
118
+ There may be times that you want to only encrypt when certain conditions are met. For example maybe you're using rails and you don't want to encrypt
119
+ attributes when you're in development mode. You can specify conditions like this:
120
+
121
+ class User < ActiveRecord::Base
122
+ attr_encrypted :email, :key => 'a secret key', :unless => Rails.env.development?
123
+ end
124
+
125
+ You can specify both `:if` and `:unless` options. If you pass a symbol representing an instance method then the result of the method will be evaluated. Any objects that respond to `:call` are evaluated as well.
126
+
127
+
128
+ ### Custom encryptor
129
+
130
+ The `Encryptor` (see http://github.com/shuber/encryptor) class is used by default. You may use your own custom encryptor by specifying
131
+ the `:encryptor`, `:encrypt_method`, and `:decrypt_method` options
132
+
133
+ Lets suppose you'd like to use this custom encryptor class:
134
+
135
+ class SillyEncryptor
136
+ def self.silly_encrypt(options)
137
+ (options[:value] + options[:secret_key]).reverse
138
+ end
139
+
140
+ def self.silly_decrypt(options)
141
+ options[:value].reverse.gsub(/#{options[:secret_key]}$/, '')
142
+ end
143
+ end
144
+
145
+ Simply set up your class like so:
146
+
147
+ class User
148
+ attr_encrypted :email, :secret_key => 'a secret key', :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt
149
+ end
150
+
151
+ Any options that you pass to `attr_encrypted` will be passed to the encryptor along with the `:value` option which contains the string to encrypt/decrypt. Notice it uses `:secret_key` instead of `:key`.
152
+
153
+
154
+ ### Custom algorithms
155
+
156
+ The default `Encryptor` uses the standard ruby OpenSSL library. It's default algorithm is `aes-256-cbc`. You can modify this by passing the `:algorithm` option to the `attr_encrypted` call like so:
157
+
158
+ class User
159
+ attr_encrypted :email, :key => 'a secret key', :algorithm => 'bf'
160
+ end
161
+
162
+ Run `openssl list-cipher-commands` to view a list of algorithms supported on your platform. See http://github.com/danpal/encryptor for more information.
163
+
164
+ aes-128-cbc
165
+ aes-128-ecb
166
+ aes-192-cbc
167
+ aes-192-ecb
168
+ aes-256-cbc
169
+ aes-256-ecb
170
+ base64
171
+ bf
172
+ bf-cbc
173
+ bf-cfb
174
+ bf-ecb
175
+ bf-ofb
176
+ cast
177
+ cast-cbc
178
+ cast5-cbc
179
+ cast5-cfb
180
+ cast5-ecb
181
+ cast5-ofb
182
+ des
183
+ des-cbc
184
+ des-cfb
185
+ des-ecb
186
+ des-ede
187
+ des-ede-cbc
188
+ des-ede-cfb
189
+ des-ede-ofb
190
+ des-ede3
191
+ des-ede3-cbc
192
+ des-ede3-cfb
193
+ des-ede3-ofb
194
+ des-ofb
195
+ des3
196
+ desx
197
+ idea
198
+ idea-cbc
199
+ idea-cfb
200
+ idea-ecb
201
+ idea-ofb
202
+ rc2
203
+ rc2-40-cbc
204
+ rc2-64-cbc
205
+ rc2-cbc
206
+ rc2-cfb
207
+ rc2-ecb
208
+ rc2-ofb
209
+ rc4
210
+ rc4-40
211
+
212
+
213
+ ### Default options
214
+
215
+ Let's imagine that you have a few attributes that you want to encrypt with different keys, but you don't like the `encrypted_#{attribute}` naming convention. Instead of having to define your class like this:
216
+
217
+ class User
218
+ attr_encrypted :email, :key => 'a secret key', :prefix => '', :suffix => '_crypted'
219
+ attr_encrypted :ssn, :key => 'a different secret key', :prefix => '', :suffix => '_crypted'
220
+ attr_encrypted :credit_card, :key => 'another secret key', :prefix => '', :suffix => '_crypted'
221
+ end
222
+
223
+ You can simply define some default options like so:
224
+
225
+ class User
226
+ attr_encrypted_options.merge!(:prefix => '', :suffix => '_crypted')
227
+ attr_encrypted :email, :key => 'a secret key'
228
+ attr_encrypted :ssn, :key => 'a different secret key'
229
+ attr_encrypted :credit_card, :key => 'another secret key'
230
+ end
231
+
232
+ This should help keep your classes clean and DRY.
233
+
234
+
235
+ ### Encoding
236
+
237
+ You're probably going to be storing your encrypted attributes somehow (e.g. filesystem, database, etc) and may run into some issues trying to store a weird
238
+ encrypted string. I've had this problem myself using MySQL. You can simply pass the `:encode` option to automatically encode/decode when encrypting/decrypting.
239
+
240
+ class User
241
+ attr_encrypted :email, :key => 'some secret key', :encode => true
242
+ end
243
+
244
+ The default encoding is `m*` (base64). You can change this by setting `:encode => 'some encoding'`. See the `Array#pack` method at http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding options.
245
+
246
+
247
+ ### Marshaling
248
+
249
+ You may want to encrypt objects other than strings (e.g. hashes, arrays, etc). If this is the case, simply pass the `:marshal` option to automatically marshal when encrypting/decrypting.
250
+
251
+ class User
252
+ attr_encrypted :credentials, :key => 'some secret key', :marshal => true
253
+ end
254
+
255
+ You may also optionally specify `:marshaler`, `:dump_method`, and `:load_method` if you want to use something other than the default `Marshal` object.
256
+
257
+
258
+ ### Encrypt/decrypt attribute methods
259
+
260
+ If you use the same key to encrypt every record (per attribute) like this:
261
+
262
+ class User
263
+ attr_encrypted :email, :key => 'a secret key'
264
+ end
265
+
266
+ Then you'll have these two class methods available for each attribute: `User.encrypt_email(email_to_encrypt)` and `User.decrypt_email(email_to_decrypt)`. This can be useful when you're using `ActiveRecord` (see below).
267
+
268
+
269
+ ### ActiveRecord
270
+
271
+ If you're using this gem with `ActiveRecord`, you get a few extra features:
272
+
273
+
274
+ #### Default options
275
+
276
+ For your convenience, the `:encode` option is set to true by default since you'll be storing everything in a database.
277
+
278
+
279
+ ### DataMapper and Sequel
280
+
281
+ Just like the default options for `ActiveRecord`, the `:encode` option is set to true by default since you'll be storing everything in a database.
282
+
283
+
284
+ ## Note on Patches/Pull Requests
285
+
286
+ * Fork the project.
287
+ * Make your feature addition or bug fix.
288
+ * Add tests for it. This is important so I don't break it in a
289
+ future version unintentionally.
290
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
291
+ * Send me a pull request. Bonus points for topic branches.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the attr_encryptor gem.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the attr_encryptor gem.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'attr_encryptor'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README*')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib/', __FILE__)
4
+ $:.unshift lib unless $:.include?(lib)
5
+
6
+ require 'attr_encryptor/version'
7
+ require 'date'
8
+
9
+ Gem::Specification.new do |s|
10
+ s.name = 'attr_encryptor'
11
+ s.version = AttrEncryptor::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.author = 'Daniel Palacio'
18
+ s.email = 'danpal@gmail.com'
19
+ s.homepage = 'http://github.com/danpal/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,spec,features}/*`.split("\n")
28
+
29
+ s.add_dependency('encryptor2', ['>= 1.1.1'])
30
+ s.add_development_dependency('activerecord', ['>= 2.0.0'])
31
+ s.add_development_dependency('datamapper')
32
+ s.add_development_dependency('mocha')
33
+ s.add_development_dependency('sequel')
34
+ s.add_development_dependency('dm-sqlite-adapter')
35
+ s.add_development_dependency('sqlite3')
36
+ end
@@ -0,0 +1,326 @@
1
+ require 'encryptor'
2
+ require 'openssl'
3
+
4
+ # Adds attr_accessors that encrypt and decrypt an object's attributes
5
+ module AttrEncryptor
6
+ autoload :Version, 'attr_encryptor/version'
7
+
8
+ def self.extended(base) # :nodoc:
9
+ base.class_eval do
10
+ include InstanceMethods
11
+ attr_writer :attr_encrypted_options
12
+ @attr_encrypted_options, @encrypted_attributes = {}, {}
13
+ end
14
+ end
15
+
16
+ # Generates attr_accessors that encrypt and decrypt attributes transparently
17
+ #
18
+ # Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
19
+ #
20
+ # :attribute => The name of the referenced encrypted attribute. For example
21
+ # <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
22
+ # attribute named 'ee' to store the encrypted email. This is useful when defining
23
+ # one attribute to encrypt at a time or when the :prefix and :suffix options
24
+ # aren't enough. 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, :password, :prefix => 'crypted_'</tt> would
28
+ # generate attributes named 'crypted_email' and 'crypted_password' to store the
29
+ # encrypted email and password. Defaults to 'encrypted_'.
30
+ #
31
+ # :suffix => A suffix used to generate the name of the referenced encrypted attributes.
32
+ # For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
33
+ # would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
34
+ # encrypted email. Defaults to ''.
35
+ #
36
+ # :key => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
37
+ # a symbol representing an instance method then the :key option will be replaced with the result of the
38
+ # method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
39
+ # Any other key types will be passed directly to the encryptor.
40
+ #
41
+ # :encode => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
42
+ # planning on storing the encrypted attributes in a database. The default encoding is 'm' (base64),
43
+ # however this can be overwritten by setting the :encode option to some other encoding string instead of
44
+ # just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
45
+ # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
46
+ #
47
+ # :default_encoding => Defaults to 'm' (base64).
48
+ #
49
+ # :marshal => If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning
50
+ # on encrypting something other than a string. Defaults to false unless you're using it with ActiveRecord
51
+ # or DataMapper.
52
+ #
53
+ # :marshaler => The object to use for marshaling. Defaults to Marshal.
54
+ #
55
+ # :dump_method => The dump method name to call on the <tt>:marshaler</tt> object to. Defaults to 'dump'.
56
+ #
57
+ # :load_method => The load method name to call on the <tt>:marshaler</tt> object. Defaults to 'load'.
58
+ #
59
+ # :encryptor => The object to use for encrypting. Defaults to Encryptor.
60
+ #
61
+ # :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'encrypt'.
62
+ #
63
+ # :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'decrypt'.
64
+ #
65
+ # :if => Attributes are only encrypted if this option evaluates to true. If you pass a symbol representing an instance
66
+ # method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
67
+ # Defaults to true.
68
+ #
69
+ # :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
70
+ # method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
71
+ # Defaults to false.
72
+ #
73
+ # You can specify your own default options
74
+ #
75
+ # class User
76
+ # # now all attributes will be encoded and marshaled by default
77
+ # attr_encrypted_options.merge!(:encode => true, :marshal => true, :some_other_option => true)
78
+ # attr_encrypted :configuration, :key => 'my secret key'
79
+ # end
80
+ #
81
+ #
82
+ # Example
83
+ #
84
+ # class User
85
+ # attr_encrypted :email, :credit_card, :key => 'some secret key'
86
+ # attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
87
+ # end
88
+ #
89
+ # @user = User.new
90
+ # @user.encrypted_email # nil
91
+ # @user.email? # false
92
+ # @user.email = 'test@example.com'
93
+ # @user.email? # true
94
+ # @user.encrypted_email # returns the encrypted version of 'test@example.com'
95
+ #
96
+ # @user.configuration = { :time_zone => 'UTC' }
97
+ # @user.encrypted_configuration # returns the encrypted version of configuration
98
+ #
99
+ # See README for more examples
100
+ def attr_encrypted(*attributes)
101
+ options = {
102
+ :prefix => 'encrypted_',
103
+ :suffix => '',
104
+ :if => true,
105
+ :unless => false,
106
+ :encode => false,
107
+ :default_encoding => 'm',
108
+ :marshal => false,
109
+ :marshaler => Marshal,
110
+ :dump_method => 'dump',
111
+ :load_method => 'load',
112
+ :encryptor => Encryptor,
113
+ :encrypt_method => 'encrypt',
114
+ :decrypt_method => 'decrypt'
115
+ }.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})
116
+
117
+ options[:encode] = options[:default_encoding] if options[:encode] == true
118
+
119
+ attributes.each do |attribute|
120
+ encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
121
+
122
+ instance_methods_as_symbols = instance_methods.collect { |method| method.to_sym }
123
+ attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
124
+ attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
125
+
126
+ attr_reader (encrypted_attribute_name.to_s + "_iv").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_iv").to_sym )
127
+ attr_writer (encrypted_attribute_name.to_s + "_iv").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_iv").to_sym )
128
+
129
+ attr_reader (encrypted_attribute_name.to_s + "_salt").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_salt").to_sym )
130
+ attr_writer (encrypted_attribute_name.to_s + "_salt").to_sym unless instance_methods_as_symbols.include?((encrypted_attribute_name.to_s + "_salt").to_sym )
131
+
132
+
133
+
134
+ define_method(attribute) do
135
+ instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
136
+ end
137
+
138
+ define_method("#{attribute}=") do |value|
139
+ iv = send("#{encrypted_attribute_name.to_s + "_iv"}")
140
+ if(iv == nil)
141
+ begin
142
+ algorithm = options[:algorithm] || "aes-256-cbc"
143
+ algo = OpenSSL::Cipher::Cipher.new(algorithm)
144
+ iv = [algo.random_iv].pack("m")
145
+ send("#{encrypted_attribute_name.to_s + "_iv"}=", iv)
146
+ rescue RuntimeError
147
+ end
148
+ end
149
+
150
+ salt = send("#{encrypted_attribute_name.to_s + "_salt"}") || send("#{encrypted_attribute_name.to_s + "_salt"}=", Time.now.to_i.to_s)
151
+ #this add's the iv and salt on the options for this instance
152
+ self.class.encrypted_attributes[attribute.to_sym] = self.class.encrypted_attributes[attribute.to_sym].merge(:iv => iv.unpack("m").first) if (iv && !iv.empty?)
153
+ self.class.encrypted_attributes[attribute.to_sym] = self.class.encrypted_attributes[attribute.to_sym].merge(:salt => salt)
154
+ send("#{encrypted_attribute_name}=", encrypt(attribute, value))
155
+ instance_variable_set("@#{attribute}", value)
156
+ end
157
+
158
+ define_method("#{attribute}?") do
159
+ value = send(attribute)
160
+ value.respond_to?(:empty?) ? !value.empty? : !!value
161
+ end
162
+ encrypted_attributes[attribute.to_sym] = options.merge(:attribute => encrypted_attribute_name)
163
+ end
164
+ end
165
+ alias_method :attr_encryptor, :attr_encrypted
166
+
167
+ # Default options to use with calls to <tt>attr_encrypted</tt>
168
+ #
169
+ # It will inherit existing options from its superclass
170
+ def attr_encrypted_options
171
+ @attr_encrypted_options ||= superclass.attr_encrypted_options.dup
172
+ end
173
+
174
+ # Checks if an attribute is configured with <tt>attr_encrypted</tt>
175
+ #
176
+ # Example
177
+ #
178
+ # class User
179
+ # attr_accessor :name
180
+ # attr_encrypted :email
181
+ # end
182
+ #
183
+ # User.attr_encrypted?(:name) # false
184
+ # User.attr_encrypted?(:email) # true
185
+ def attr_encrypted?(attribute)
186
+ encrypted_attributes.has_key?(attribute.to_sym)
187
+ end
188
+
189
+ # Decrypts a value for the attribute specified
190
+ #
191
+ # Example
192
+ #
193
+ # class User
194
+ # attr_encrypted :email
195
+ # end
196
+ #
197
+ # email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
198
+ def decrypt(attribute, encrypted_value, options = {})
199
+ options = encrypted_attributes[attribute.to_sym].merge(options)
200
+ if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
201
+ encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
202
+ value = options[:encryptor].send(options[:decrypt_method], options.merge!(:value => encrypted_value))
203
+ value = options[:marshaler].send(options[:load_method], value) if options[:marshal]
204
+ value
205
+ else
206
+ encrypted_value
207
+ end
208
+ end
209
+
210
+ # Encrypts a value for the attribute specified
211
+ #
212
+ # Example
213
+ #
214
+ # class User
215
+ # attr_encrypted :email
216
+ # end
217
+ #
218
+ # encrypted_email = User.encrypt(:email, 'test@example.com')
219
+ def encrypt(attribute, value, options = {})
220
+ options = encrypted_attributes[attribute.to_sym].merge(options)
221
+ if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
222
+ value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
223
+ encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(:value => value))
224
+ encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
225
+ encrypted_value
226
+ else
227
+ value
228
+ end
229
+ end
230
+
231
+ # Contains a hash of encrypted attributes with virtual attribute names as keys
232
+ # and their corresponding options as values
233
+ #
234
+ # Example
235
+ #
236
+ # class User
237
+ # attr_encrypted :email, :key => 'my secret key'
238
+ # end
239
+ #
240
+ # User.encrypted_attributes # { :email => { :attribute => 'encrypted_email', :key => 'my secret key' } }
241
+ def encrypted_attributes
242
+ @encrypted_attributes ||= superclass.encrypted_attributes.dup
243
+ end
244
+
245
+ # Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
246
+ # if attribute was configured with attr_encrypted
247
+ #
248
+ # Example
249
+ #
250
+ # class User
251
+ # attr_encrypted :email, :key => 'my secret key'
252
+ # end
253
+ #
254
+ # User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
255
+ def method_missing(method, *arguments, &block)
256
+ if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
257
+ send($1, $3, *arguments)
258
+ else
259
+ super
260
+ end
261
+ end
262
+
263
+ module InstanceMethods
264
+ # Decrypts a value for the attribute specified using options evaluated in the current object's scope
265
+ #
266
+ # Example
267
+ #
268
+ # class User
269
+ # attr_accessor :secret_key
270
+ # attr_encrypted :email, :key => :secret_key
271
+ #
272
+ # def initialize(secret_key)
273
+ # self.secret_key = secret_key
274
+ # end
275
+ # end
276
+ #
277
+ # @user = User.new('some-secret-key')
278
+ # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
279
+ def decrypt(attribute, encrypted_value)
280
+ self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
281
+ end
282
+
283
+ # Encrypts a value for the attribute specified using options evaluated in the current object's scope
284
+ #
285
+ # Example
286
+ #
287
+ # class User
288
+ # attr_accessor :secret_key
289
+ # attr_encrypted :email, :key => :secret_key
290
+ #
291
+ # def initialize(secret_key)
292
+ # self.secret_key = secret_key
293
+ # end
294
+ # end
295
+ #
296
+ # @user = User.new('some-secret-key')
297
+ # @user.encrypt(:email, 'test@example.com')
298
+ def encrypt(attribute, value)
299
+ self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
300
+ end
301
+
302
+ protected
303
+
304
+ # Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
305
+ def evaluated_attr_encrypted_options_for(attribute)
306
+ self.class.encrypted_attributes[attribute.to_sym].inject({}) { |hash, (option, value)| hash.merge!(option => evaluate_attr_encrypted_option(value)) }
307
+ end
308
+
309
+ # Evaluates symbol (method reference) or proc (responds to call) options
310
+ #
311
+ # If the option is not a symbol or proc then the original option is returned
312
+ def evaluate_attr_encrypted_option(option)
313
+ if option.is_a?(Symbol) && respond_to?(option)
314
+ send(option)
315
+ elsif option.respond_to?(:call)
316
+ option.call(self)
317
+ else
318
+ option
319
+ end
320
+ end
321
+ end
322
+ end
323
+
324
+ Object.extend AttrEncryptor
325
+
326
+ Dir[File.join(File.dirname(__FILE__), 'attr_encryptor', 'adapters', '*.rb')].each { |adapter| require adapter }