symmetric-encryption 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2012 Clarity Services, Inc.
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ symmetric-encryption
2
+ ====================
3
+
4
+ * http://github.com/ClarityServices/symmetric-encryption
5
+
6
+ ### Introduction
7
+
8
+ Any project that wants to meet PCI compliance has to ensure that the data is encrypted
9
+ whilst in flight and at rest. Amongst many other other requirements all passwords
10
+ in configuration files have to be encrypted
11
+
12
+ This Gem helps achieve compliance by supporting encryption of data in a simple
13
+ and consistent way
14
+
15
+ ### Features
16
+
17
+ * Encryption of passwords in configuration files
18
+ * attr_encrypted replacement
19
+ * Externalization of symmetric encryption keys so that they are not in the
20
+ source code
21
+
22
+ ### Install
23
+
24
+ gem install symmetric-encryption
25
+
26
+ Meta
27
+ ----
28
+
29
+ * Code: `git clone git://github.com/ClarityServices/symmetric-encryption.git`
30
+ * Home: <https://github.com/ClarityServices/symmetric-encryption>
31
+ * Docs: TODO <http://ClarityServices.github.com/symmetric-encryption/>
32
+ * Bugs: <http://github.com/reidmorrison/symmetric-encryption/issues>
33
+ * Gems: <http://rubygems.org/gems/symmetric-encryption>
34
+
35
+ This project uses [Semantic Versioning](http://semver.org/).
36
+
37
+ Authors
38
+ -------
39
+
40
+ Reid Morrison :: reidmo@gmail.com :: @reidmorrison
41
+
42
+ License
43
+ -------
44
+
45
+ Copyright 2012 Clarity Services, Inc.
46
+
47
+ Licensed under the Apache License, Version 2.0 (the "License");
48
+ you may not use this file except in compliance with the License.
49
+ You may obtain a copy of the License at
50
+
51
+ http://www.apache.org/licenses/LICENSE-2.0
52
+
53
+ Unless required by applicable law or agreed to in writing, software
54
+ distributed under the License is distributed on an "AS IS" BASIS,
55
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
56
+ See the License for the specific language governing permissions and
57
+ limitations under the License.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rake/clean'
2
+ require 'rake/testtask'
3
+ require 'date'
4
+
5
+ desc "Build gem"
6
+ task :gem do |t|
7
+ gemspec = Gem::Specification.new do |s|
8
+ s.name = 'symmetric-encryption'
9
+ s.version = '0.0.1'
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ['Reid Morrison']
12
+ s.email = ['reidmo@gmail.com']
13
+ s.homepage = 'https://github.com/ClarityServices/symmetric-encryption'
14
+ s.date = Date.today.to_s
15
+ s.summary = "Symmetric Encryption for Ruby, and Ruby on Rails"
16
+ s.description = "Symmetric Encryption is a library to seamlessly enable symmetric encryption in a project, written in Ruby."
17
+ s.files = FileList["./**/*"].exclude('*.gem', './nbproject/*').map{|f| f.sub(/^\.\//, '')}
18
+ s.has_rdoc = true
19
+ end
20
+ Gem::Builder.new(gemspec).build
21
+ end
22
+
23
+ desc "Run Test Suite"
24
+ task :test do
25
+ Rake::TestTask.new(:functional) do |t|
26
+ t.test_files = FileList['test/*_test.rb']
27
+ t.verbose = true
28
+ end
29
+
30
+ Rake::Task['functional'].invoke
31
+ end
@@ -0,0 +1,5 @@
1
+ require 'symmetric/encryption'
2
+ if defined?(Rails)
3
+ require 'symmetric/railtie'
4
+ require "symmetric/extensions/active_record/base"
5
+ end
@@ -0,0 +1,221 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'zlib'
4
+
5
+ module Symmetric
6
+
7
+ # Encrypt using 256 Bit AES CBC symmetric key and initialization vector
8
+ # The symmetric key is protected using the private key below and must
9
+ # be distributed separately from the application
10
+ class Encryption
11
+
12
+ # Binary encrypted data includes this magic header so that we can quickly
13
+ # identify binary data versus base64 encoded data that does not have this header
14
+ unless defined? MAGIC_HEADER
15
+ MAGIC_HEADER = '@EnC'
16
+ MAGIC_HEADER_SIZE = MAGIC_HEADER.size
17
+ end
18
+
19
+ # Set the Symmetric Cipher to be used
20
+ def self.cipher=(cipher)
21
+ @@cipher = cipher
22
+ end
23
+
24
+ # Returns the Symmetric Cipher being used
25
+ def self.cipher
26
+ @@cipher
27
+ end
28
+
29
+ # Set the Symmetric Key to use for encryption and decryption
30
+ def self.key=(key)
31
+ @@key = key
32
+ end
33
+
34
+ # Set the Initialization Vector to use with Symmetric Key
35
+ def self.iv=(iv)
36
+ @@iv = iv
37
+ end
38
+
39
+ # Defaults
40
+ @@key = nil
41
+ @@iv = nil
42
+
43
+ # Load the Encryption Configuration from a YAML file
44
+ # filename: Name of file to read.
45
+ # Mandatory for non-Rails apps
46
+ # Default: Rails.root/config/symmetry.yml
47
+ def self.load!(filename=nil, environment=nil)
48
+ config = YAML.load_file(filename || File.join(Rails.root, "config", "symmetric-encryption.yml"))[environment || Rails.env]
49
+ self.cipher = config['cipher'] || 'aes-256-cbc'
50
+ symmetric_key = config['symmetric_key']
51
+ symmetric_iv = config['symmetric_iv']
52
+
53
+ # Hard coded symmetric_key?
54
+ if symmetric_key
55
+ self.key = symmetric_key
56
+ self.iv = symmetric_iv
57
+ else
58
+ load_keys(config['key_filename'], config['iv_filename'], config['private_key'])
59
+ end
60
+
61
+ end
62
+
63
+ # Load the symmetric key to use for encrypting and decrypting data
64
+ # Call from environment.rb before calling encrypt or decrypt
65
+ #
66
+ # private_key: Key used to unlock file containing the actual symmetric key
67
+ def self.load_keys(key_filename, iv_filename, private_key)
68
+ # Load Encrypted Symmetric keys
69
+ encrypted_key = File.read(key_filename)
70
+ encrypted_iv = File.read(iv_filename)
71
+
72
+ # Decrypt Symmetric Key
73
+ rsa = OpenSSL::PKey::RSA.new(private_key)
74
+ @@key = rsa.private_decrypt(encrypted_key)
75
+ @@iv = rsa.private_decrypt(encrypted_iv)
76
+ nil
77
+ end
78
+
79
+ # Generate new random keys for use with this Encryption library
80
+ #
81
+ # Creates:
82
+ # 2048 bit Private Key private.key
83
+ # 2048 bit Public Key public.key
84
+ #
85
+ # Symmetric Key .key
86
+ # and initilization vector .iv
87
+ # which is encrypted with the above Public key
88
+ #
89
+ # Note: Existing files will be overwritten
90
+ def self.generate_key_files(symmetric_keys_path='.', rsa_keys_path='.', cipher='aes-256-cbc')
91
+ # Generate Asymmetric key pair
92
+ new_key = OpenSSL::PKey::RSA.generate(2048)
93
+ # To ensure compatibility with C openssl code, remove RSA from pub file headers
94
+ pub_key = new_key.public_key.export.gsub('RSA PUBLIC','PUBLIC')
95
+ File.open(File.join(rsa_keys_path, 'public.key'), 'w') {|file| file.write(pub_key)}
96
+ File.open(File.join(rsa_keys_path, 'private.key'), 'w') {|file| file.write(new_key.to_pem)}
97
+
98
+ # Generate Symmetric Key
99
+ cipher = OpenSSL::Cipher::Cipher.new(cipher)
100
+ cipher.encrypt
101
+ @@key = cipher.random_key
102
+ @@iv = cipher.random_iv
103
+
104
+ # Save symmetric key after encrypting it with the private asymmetric key
105
+ File.open(File.join(symmetric_keys_path, '.key'), 'wb') {|file| file.write( OpenSSL::PKey::RSA.new(new_key.public_key).public_encrypt(@@key) ) }
106
+ File.open(File.join(symmetric_keys_path, '.iv'), 'wb') {|file| file.write( OpenSSL::PKey::RSA.new(new_key.public_key).public_encrypt(@@iv) ) }
107
+ Rails.logger.info("Generated new Private, Public and Symmetric Key for encryption. Please copy #{filename} to the other servers.")
108
+ end
109
+
110
+ # Generate a 22 character random password
111
+ def self.random_password
112
+ Base64.encode64(OpenSSL::Cipher::Cipher.new('aes-128-cbc').random_key)[0..-4]
113
+ end
114
+
115
+ # AES Symmetric Decryption of supplied string
116
+ # Returns decrypted string
117
+ # Returns nil if the supplied str is nil
118
+ # Returns "" if it is a string and it is empty
119
+ def self.decrypt(str)
120
+ return str if str.nil? || (str.is_a?(String) && str.empty?)
121
+ self.crypt(:decrypt, Base64.decode64(str))
122
+ end
123
+
124
+ # AES Symmetric Encryption of supplied string
125
+ # Returns result as a Base64 encoded string
126
+ # Returns nil if the supplied str is nil
127
+ # Returns "" if it is a string and it is empty
128
+ def self.encrypt(str)
129
+ return str if str.nil? || (str.is_a?(String) && str.empty?)
130
+ Base64.encode64(self.crypt(:encrypt, str))
131
+ end
132
+
133
+ # Invokes decrypt
134
+ # Returns decrypted String
135
+ # Return nil if it fails to decrypt a String
136
+ #
137
+ # Useful for example when decoding passwords encrypted using a key from a
138
+ # different environment. I.e. We cannot decode production passwords
139
+ # in the test or development environments but still need to be able to load
140
+ # YAML config files that contain encrypted development and production passwords
141
+ def self.try_decrypt(str)
142
+ self.decrypt(str) rescue nil
143
+ end
144
+
145
+ # AES Symmetric Encryption of supplied string
146
+ # Returns result as a binary encrypted string
147
+ # Returns nil if the supplied str is nil or empty
148
+ # Parameters
149
+ # compress => Whether to compress the supplied string using zip before
150
+ # encrypting
151
+ # true | false
152
+ # Default false
153
+ def self.encrypt_binary(str, compress=false)
154
+ return nil if str.nil? || (str.is_a?(String) && str.empty?)
155
+ # Bit Layout
156
+ # 15 => Compressed?
157
+ # 0..14 => Version number of encryption key/algorithm currently 0
158
+ flags = 0 # Same as 0b0000_0000_0000_0000
159
+ # If the data is to be compressed before being encrypted, set the flag and
160
+ # compress using zlib. Only compress if data is greater than 15 chars
161
+ str = str.to_s unless str.is_a?(String)
162
+ if compress && str.length > 15
163
+ flags |= 0b1000_0000_0000_0000
164
+ begin
165
+ ostream = StringIO.new
166
+ gz = Zlib::GzipWriter.new(ostream)
167
+ gz.write(str)
168
+ str = ostream.string
169
+ ensure
170
+ gz.close
171
+ end
172
+ end
173
+ return nil unless encrypted = self.crypt(:encrypt, str)
174
+ # Resulting buffer consists of:
175
+ # '@EnC'
176
+ # unsigned short (32 bits) in little endian format for flags above
177
+ # 'actual encrypted buffer data'
178
+ "#{MAGIC_HEADER}#{[flags].pack('v')}#{encrypted}"
179
+ end
180
+
181
+ # AES Symmetric Decryption of supplied Binary string
182
+ # Returns decrypted string
183
+ # Returns nil if the supplied str is nil
184
+ # Returns "" if it is a string and it is empty
185
+ def self.decrypt_binary(str)
186
+ return str if str.nil? || (str.is_a?(String) && str.empty?)
187
+ str = str.to_s unless str.is_a?(String)
188
+ encrypted = if str.starts_with? MAGIC_HEADER
189
+ # Remove header and extract flags
190
+ header, flags = str.unpack(@@unpack ||= "A#{MAGIC_HEADER_SIZE}v")
191
+ # Uncompress if data is compressed and remove header
192
+ if flags & 0b1000_0000_0000_0000
193
+ begin
194
+ gz = Zlib::GzipReader.new(StringIO.new(str[MAGIC_HEADER_SIZE,-1]))
195
+ gz.read
196
+ ensure
197
+ gz.close
198
+ end
199
+ else
200
+ str[MAGIC_HEADER_SIZE,-1]
201
+ end
202
+ else
203
+ Base64.decode64(str)
204
+ end
205
+ self.crypt(:decrypt, encrypted)
206
+ end
207
+
208
+ protected
209
+
210
+ def self.crypt(cipher_method, string) #:nodoc:
211
+ openssl_cipher = OpenSSL::Cipher::Cipher.new(self.cipher)
212
+ openssl_cipher.send(cipher_method)
213
+ raise "Encryption.key must be set before calling Encryption encrypt or decrypt" unless @@key
214
+ openssl_cipher.key = @@key
215
+ openssl_cipher.iv = @@iv if @@iv
216
+ result = openssl_cipher.update(string.to_s)
217
+ result << openssl_cipher.final
218
+ end
219
+
220
+ end
221
+ end
@@ -0,0 +1,144 @@
1
+ module ActiveRecord #:nodoc:
2
+ class Base
3
+
4
+ class << self # Class methods
5
+ # Much lighter weight encryption for Rails attributes matching the
6
+ # attr_encrypted interface using Symmetry::Encryption
7
+ #
8
+ # The regular attr_encrypted gem uses Encryptor that adds encryption to
9
+ # every Ruby object which is a complete overkill for this simple use-case
10
+ #
11
+ # Params:
12
+ # * symbolic names of each method to create which has a corresponding
13
+ # method already defined in rails starting with: encrypted_
14
+ # * Followed by an option hash:
15
+ # :marshal => Whether this element should be converted to YAML before encryption
16
+ # true or false
17
+ # Default: false
18
+ #
19
+ def attr_encrypted(*params)
20
+ # Ensure ActiveRecord has created all its methods first
21
+ # Ignore failures since the table may not yet actually exist
22
+ define_attribute_methods rescue nil
23
+
24
+ options = params.last.is_a?(Hash) ? params.pop : {}
25
+
26
+ params.each do |attribute|
27
+ # Generate unencrypted attribute with getter and setter
28
+ class_eval <<-UNENCRYPTED_GETTER
29
+ def #{attribute}
30
+ @#{attribute} = ::Symmetric::Encryption.decrypt(self.encrypted_#{attribute}) if @#{attribute}.nil? && !self.encrypted_#{attribute}.nil?
31
+ @#{attribute}
32
+ end
33
+ UNENCRYPTED_GETTER
34
+
35
+ # Encrypt value immediately when unencrypted value is set
36
+ # Unencrypted value is also kept for performance reasons
37
+ class_eval <<-UNENCRYPTED_SETTER
38
+ def #{attribute}=(value)
39
+ self.encrypted_#{attribute} = ::Symmetric::Encryption.encrypt(value#{".to_yaml" if options[:marshal]})
40
+ @#{attribute} = value
41
+ end
42
+ UNENCRYPTED_SETTER
43
+
44
+ encrypted_attributes[attribute.to_sym] = "encrypted_#{attribute}".to_sym
45
+ end
46
+ end
47
+
48
+ # Contains a hash of encrypted attributes with virtual attribute names as keys and real attribute
49
+ # names as values
50
+ #
51
+ # Example
52
+ #
53
+ # class User < ActiveRecord::Base
54
+ # attr_encrypted :email
55
+ # end
56
+ #
57
+ # User.encrypted_attributes # { :email => :encrypted_email }
58
+ def encrypted_attributes
59
+ @encrypted_attributes ||= superclass.respond_to?(:encrypted_attributes) ? superclass.encrypted_attributes.dup : {}
60
+ end
61
+
62
+ # Return the name of all encrypted virtual attributes as an Array of symbols
63
+ # Example: [:email, :password]
64
+ def encrypted_keys
65
+ @encrypted_keys ||= encrypted_attributes.keys
66
+ end
67
+
68
+ # Return the name of all encrypted columns as an Array of symbols
69
+ # Example: [:encrypted_email, :encrypted_password]
70
+ def encrypted_columns
71
+ @encrypted_columns ||= encrypted_attributes.values
72
+ end
73
+
74
+ # Returns whether an attribute has been configured to be encrypted
75
+ #
76
+ # Example
77
+ #
78
+ # class User < ActiveRecord::Base
79
+ # attr_accessor :name
80
+ # attr_encrypted :email
81
+ # end
82
+ #
83
+ # User.encrypted_attribute?(:name) # false
84
+ # User.encrypted_attribute?(:email) # true
85
+ def encrypted_attribute?(attribute)
86
+ encrypted_keys.include?(attribute)
87
+ end
88
+
89
+ # Returns whether the attribute is the database column to hold the
90
+ # encrypted data for a matching encrypted attribute
91
+ #
92
+ # Example
93
+ #
94
+ # class User < ActiveRecord::Base
95
+ # attr_accessor :name
96
+ # attr_encrypted :email
97
+ # end
98
+ #
99
+ # User.encrypted_column?(:encrypted_name) # false
100
+ # User.encrypted_column?(:encrypted_email) # true
101
+ def encrypted_column?(attribute)
102
+ encrypted_columns.include?(attribute)
103
+ end
104
+
105
+ protected
106
+
107
+ # Allows you to use dynamic methods like <tt>find_by_email</tt> or <tt>scoped_by_email</tt> for
108
+ # encrypted attributes
109
+ #
110
+ # This is useful for encrypting fields like email addresses. Your user's email addresses
111
+ # are encrypted in the database, but you can still look up a user by email for logging in
112
+ #
113
+ # Example
114
+ #
115
+ # class User < ActiveRecord::Base
116
+ # attr_encrypted :email
117
+ # end
118
+ #
119
+ # User.find_by_email_and_password('test@example.com', 'testing')
120
+ # # results in a call to
121
+ # User.find_by_encrypted_email_and_password('the_encrypted_version_of_test@example.com', 'testing')
122
+ def method_missing_with_attr_encrypted(method, *args, &block)
123
+ if match = /^(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)$/.match(method.to_s)
124
+ attribute_names = match.captures.last.split('_and_')
125
+ attribute_names.each_with_index do |attribute, index|
126
+ encrypted_name = "encrypted_#{attribute}"
127
+ if instance_methods.include? encrypted_name #.to_sym in 1.9
128
+ args[index] = ::Symmetric::Encryption.encrypt(args[index])
129
+ attribute_names[index] = encrypted_name
130
+ end
131
+ end
132
+ method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
133
+ end
134
+ method_missing_without_attr_encrypted(method, *args, &block)
135
+ end
136
+
137
+ alias_method_chain :method_missing, :attr_encrypted
138
+ #Equivalent to:
139
+ # alias_method :method_missing_without_attr_encrypted, :attr_encrypted # new, old
140
+ # alias_method :attr_encrypted, :method_missing_with_attr_encrypted
141
+
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ module Symmetric #:nodoc:
3
+ class Railtie < Rails::Railtie #:nodoc:
4
+
5
+ # Exposes Symmetric Encryption's configuration to the Rails application configuration.
6
+ #
7
+ # @example Set up configuration in the Rails app.
8
+ # module MyApplication
9
+ # class Application < Rails::Application
10
+ # config.symmetric_encryption.cipher = 'aes-256-cbc'
11
+ # end
12
+ # end
13
+ #config.symmetric_encryption = ::Symmetric::Config
14
+
15
+ rake_tasks do
16
+ load "symmetric/railties/symmetric-encryption.rake"
17
+ end
18
+
19
+ # Initialize Symmetry. This will look for a symmetry.yml in the config
20
+ # directory and configure Symmetry appropriately.
21
+ #
22
+ # @example symmetry.yml
23
+ #
24
+ # development:
25
+ # cipher: aes-256-cbc
26
+ # symmetric_key: 1234567890ABCDEF1234567890ABCDEF
27
+ # symmetric_iv: 1234567890ABCDEF
28
+ #
29
+ initializer "load symmetry encryption keys" do
30
+ config.before_initialize do
31
+ config_file = Rails.root.join("config", "symmetric-encryption.yml")
32
+ if config_file.file?
33
+ ::Symmetric::Encryption.load!(config_file, Rails.env)
34
+ else
35
+ puts "\nSymmetric Encryption config not found. Create a config file at: config/symmetric-encryption.yml"
36
+ # puts "to generate one run: rails generate symmetric-encryption:config\n\n"
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,33 @@
1
+ namespace 'symmetric-encryption' do
2
+
3
+ desc 'Decrypt the supplied string. Example: VALUE="Hello World" rake symmetric-encryption:decrypt'
4
+ task :decrypt do
5
+ puts "\nEncrypted: #{ENV['VALUE']}"
6
+ puts "Decrypted: #{Symmetric::Encryption.decrypt(ENV['VALUE'])}\n\n"
7
+ end
8
+
9
+ desc 'Encrypt a value, such as a password. Example: rake symmetric-encryption:encrypt'
10
+ task :encrypt do
11
+ require 'highline'
12
+ password1 = nil
13
+ password2 = 0
14
+
15
+ while password1 != password2
16
+ password1 = HighLine.new.ask("Enter the value to encrypt:") { |q| q.echo = "*" }
17
+ password2 = HighLine.new.ask("Re-enter the value to encrypt:") { |q| q.echo = "*" }
18
+
19
+ if (password1 != password2)
20
+ puts "Passwords do not match, please try again"
21
+ end
22
+ end
23
+ puts "\nEncrypted: #{Symmetric::Encryption.encrypt(password1)}\n\n"
24
+ end
25
+
26
+ desc 'Generate a random password and display its encrypted form'
27
+ task :random_password do
28
+ p = Symmetric::Encryption.random_password
29
+ puts "\nGenerated Password: #{p}"
30
+ puts "Encrypted: #{Symmetric::Encryption.encrypt(p)}\n\n"
31
+ end
32
+
33
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ file.reference.symmetry-lib=/Users/rmorrison/Sandbox/symmetry/lib
2
+ file.reference.symmetry-test=/Users/rmorrison/Sandbox/symmetry/test
3
+ platform.active=JRuby
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project-private xmlns="http://www.netbeans.org/ns/project-private/1">
3
+ <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
4
+ </project-private>
@@ -0,0 +1,4 @@
1
+ clean=
2
+ clobber=
3
+ gem=
4
+ test=
@@ -0,0 +1,102 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'shoulda'
7
+ require 'symmetric-encryption'
8
+ require 'yaml'
9
+
10
+ # #TODO Need to supply the model and migrations for this test
11
+ class User
12
+ attr_encrypted :bank_account_number
13
+ attr_encrypted :social_security_number
14
+ end
15
+
16
+ # Unit Test for Symmetric::Encryption
17
+ #
18
+ class EncryptionTest < ActiveSupport::TestCase
19
+ context 'the Symmetric::Encryption Library' do
20
+
21
+ setup do
22
+ @bank_account_number = "1234567890"
23
+ @bank_account_number_encrypted = "V8Dg6zeeIpDg4+qrn2mjlA==\n"
24
+
25
+ @social_security_number = "987654321"
26
+ @social_security_number_encrypted = "Qd0qzN6oVuATJQBTf8X6tg==\n"
27
+
28
+ @user = User.new(
29
+ # Encrypted Attribute
30
+ :bank_account_number => @bank_account_number,
31
+ # Encrypted Attribute
32
+ :social_security_number => @social_security_number
33
+ )
34
+ end
35
+
36
+ should "encrypt simple string" do
37
+ assert_equal @social_security_number_encrypted, Symmetric::Encryption.encrypt(@social_security_number)
38
+ end
39
+
40
+ should "decrypt string" do
41
+ assert_equal @social_security_number, Symmetric::Encryption.decrypt(@social_security_number_encrypted)
42
+ end
43
+
44
+ should "have encrypted methods" do
45
+ assert_equal true, @user.respond_to?(:encrypted_bank_account_number)
46
+ assert_equal true, @user.respond_to?(:encrypted_social_security_number)
47
+ assert_equal false, @user.respond_to?(:encrypted_name)
48
+ end
49
+
50
+ should "have unencrypted values" do
51
+ assert_equal @bank_account_number, @user.bank_account_number
52
+ assert_equal @social_security_number, @user.social_security_number
53
+ end
54
+
55
+ should "have encrypted values" do
56
+ assert_equal @bank_account_number_encrypted, @user.encrypted_bank_account_number
57
+ assert_equal @social_security_number_encrypted, @user.encrypted_social_security_number
58
+ end
59
+
60
+ should "encrypt" do
61
+ user = User.new
62
+ user.bank_account_number = @bank_account_number
63
+ assert_equal @bank_account_number, user.bank_account_number
64
+ assert_equal @bank_account_number_encrypted, user.encrypted_bank_account_number
65
+ end
66
+
67
+ should "allow lookups using unencrypted or encrypted column name" do
68
+ user_id = @user.save!
69
+
70
+ inq = User.find_by_bank_account_number(@bank_account_number)
71
+ assert_equal @bank_account_number, inq.bank_account_number
72
+ assert_equal @bank_account_number_encrypted, inq.encrypted_bank_account_number
73
+
74
+ User.delete(user_id)
75
+ end
76
+
77
+ should "return encrypted attributes for the class" do
78
+ expect = {:social_security_number=>:encrypted_social_security_number, :bank_account_number=>:encrypted_bank_account_number, :check_bank_account_number=>:encrypted_check_bank_account_number}
79
+ result = User.encrypted_attributes
80
+ expect.each_pair {|k,v| assert_equal expect[k], result[k]}
81
+ end
82
+
83
+ should "return encrypted keys for the class" do
84
+ expect = [:social_security_number, :bank_account_number, :check_bank_account_number]
85
+ result = User.encrypted_keys
86
+ expect.each {|val| assert_equal true, result.include?(val)}
87
+
88
+ # Also check encrypted_attribute?
89
+ expect.each {|val| assert_equal true, User.encrypted_attribute?(val)}
90
+ end
91
+
92
+ should "return encrypted columns for the class" do
93
+ expect = [:encrypted_social_security_number, :encrypted_bank_account_number, :encrypted_check_bank_account_number]
94
+ result = User.encrypted_columns
95
+ expect.each {|val| assert_equal true, result.include?(val)}
96
+
97
+ # Also check encrypted_column?
98
+ expect.each {|val| assert_equal true, User.encrypted_column?(val)}
99
+ end
100
+
101
+ end
102
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: symmetric-encryption
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Reid Morrison
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-01-18 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description: Symmetric Encryption is a library to seamlessly enable symmetric encryption in a project, written in Ruby.
17
+ email:
18
+ - reidmo@gmail.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - LICENSE.txt
27
+ - Rakefile
28
+ - README.md
29
+ - symmetric-encryption-0.0.1.gem
30
+ - lib/symmetric-encryption.rb
31
+ - lib/symmetric/encryption.rb
32
+ - lib/symmetric/railtie.rb
33
+ - lib/symmetric/extensions/active_record/base.rb
34
+ - lib/symmetric/railties/symmetric-encryption.rake
35
+ - nbproject/private/config.properties
36
+ - nbproject/private/private.properties
37
+ - nbproject/private/private.xml
38
+ - nbproject/private/rake-d.txt
39
+ - test/encryption_test.rb
40
+ homepage: https://github.com/ClarityServices/symmetric-encryption
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.9
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Symmetric Encryption for Ruby, and Ruby on Rails
67
+ test_files: []
68
+