symmetric-encryption 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 4
7
+ - 5
8
8
  - 0
9
- version: 0.4.0
9
+ version: 0.5.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Reid Morrison
@@ -14,11 +14,11 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-03-20 00:00:00 -04:00
17
+ date: 2012-04-05 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
21
- description: Symmetric Encryption is a library to seamlessly enable symmetric encryption in a project, written in Ruby.
21
+ description: SymmetricEncryption supports encrypting ActiveRecord data, Mongoid data, passwords in configuration files, encrypting and decrypting of large files through streaming
22
22
  email:
23
23
  - reidmo@gmail.com
24
24
  executables: []
@@ -29,15 +29,17 @@ extra_rdoc_files: []
29
29
 
30
30
  files:
31
31
  - examples/symmetric-encryption.yml
32
- - lib/symmetric/cipher.rb
33
- - lib/symmetric/encryption.rb
34
- - lib/symmetric/extensions/active_record/base.rb
35
- - lib/symmetric/extensions/mongoid/fields.rb
36
- - lib/symmetric/railtie.rb
37
- - lib/symmetric/railties/symmetric_encrypted_validator.rb
38
- - lib/symmetric/railties/symmetric_encryption.rake
39
- - lib/symmetric/version.rb
40
32
  - lib/symmetric-encryption.rb
33
+ - lib/symmetric_encryption/cipher.rb
34
+ - lib/symmetric_encryption/extensions/active_record/base.rb
35
+ - lib/symmetric_encryption/extensions/mongoid/fields.rb
36
+ - lib/symmetric_encryption/railtie.rb
37
+ - lib/symmetric_encryption/railties/symmetric_encryption.rake
38
+ - lib/symmetric_encryption/railties/symmetric_encryption_validator.rb
39
+ - lib/symmetric_encryption/reader.rb
40
+ - lib/symmetric_encryption/symmetric_encryption.rb
41
+ - lib/symmetric_encryption/version.rb
42
+ - lib/symmetric_encryption/writer.rb
41
43
  - LICENSE.txt
42
44
  - nbproject/private/config.properties
43
45
  - nbproject/private/private.properties
@@ -47,8 +49,9 @@ files:
47
49
  - nbproject/project.xml
48
50
  - Rakefile
49
51
  - README.md
50
- - symmetric-encryption-0.3.0.gem
51
- - symmetric-encryption-0.3.1.gem
52
+ - symmetric-encryption-0.2.0.gem
53
+ - symmetric-encryption-0.4.0.gem
54
+ - symmetric-encryption-0.5.0.gem
52
55
  - test/attr_encrypted_test.rb
53
56
  - test/cipher_test.rb
54
57
  - test/config/database.yml
@@ -58,8 +61,10 @@ files:
58
61
  - test/config/test_new.key
59
62
  - test/config/test_secondary_1.iv
60
63
  - test/config/test_secondary_1.key
61
- - test/encryption_test.rb
62
64
  - test/field_encrypted_test.rb
65
+ - test/reader_test.rb
66
+ - test/symmetric_encryption_test.rb
67
+ - test/writer_test.rb
63
68
  has_rdoc: true
64
69
  homepage: https://github.com/ClarityServices/symmetric-encryption
65
70
  licenses: []
@@ -1,184 +0,0 @@
1
- require 'base64'
2
- require 'openssl'
3
- require 'zlib'
4
-
5
- module Symmetric
6
-
7
- # Hold all information related to encryption keys
8
- # as well as encrypt and decrypt data using those keys
9
- #
10
- # Cipher is thread safe so that the same instance can be called by multiple
11
- # threads at the same time without needing an instance of Cipher per thread
12
- class Cipher
13
- # Cipher to use for encryption and decryption
14
- attr_reader :cipher
15
-
16
- # Future Use:
17
- # attr_accessor :encoding, :version
18
-
19
- # Generate a new Symmetric Key pair
20
- #
21
- # Returns a hash containing a new random symmetric_key pair
22
- # consisting of a :key and :iv.
23
- # The cipher is also included for compatibility with the Cipher initializer
24
- def self.random_key_pair(cipher = 'aes-256-cbc', generate_iv = true)
25
- openssl_cipher = OpenSSL::Cipher.new(cipher)
26
- openssl_cipher.encrypt
27
-
28
- {
29
- :key => openssl_cipher.random_key,
30
- :iv => generate_iv ? openssl_cipher.random_iv : nil,
31
- :cipher => cipher
32
- }
33
- end
34
-
35
- # Create a Symmetric::Key for encryption and decryption purposes
36
- #
37
- # Parameters:
38
- # :key
39
- # The Symmetric Key to use for encryption and decryption
40
- # :iv
41
- # Optional. The Initialization Vector to use with Symmetric Key
42
- # :cipher
43
- # Optional. Encryption Cipher to use
44
- # Default: aes-256-cbc
45
- def initialize(parms={})
46
- raise "Missing mandatory parameter :key" unless @key = parms[:key]
47
- @iv = parms[:iv]
48
- @cipher = parms[:cipher] || 'aes-256-cbc'
49
- end
50
-
51
- # AES Symmetric Encryption of supplied string
52
- # Returns result as a Base64 encoded string
53
- # Returns nil if the supplied str is nil
54
- # Returns "" if it is a string and it is empty
55
- def encrypt(str)
56
- return str if str.nil? || (str.is_a?(String) && str.empty?)
57
- ::Base64.encode64(crypt(:encrypt, str))
58
- end
59
-
60
- # AES Symmetric Decryption of supplied string
61
- # Returns decrypted string
62
- # Returns nil if the supplied str is nil
63
- # Returns "" if it is a string and it is empty
64
- def decrypt(str)
65
- return str if str.nil? || (str.is_a?(String) && str.empty?)
66
- crypt(:decrypt, ::Base64.decode64(str))
67
- end
68
-
69
- # The minimum length for an encrypted string
70
- def min_encrypted_length
71
- @min_encrypted_length ||= encrypt('1').length
72
- end
73
-
74
- # Returns [true|false] a best effort determination as to whether the supplied
75
- # string is encrypted or not, without incurring the penalty of actually
76
- # decrypting the supplied data
77
- # Parameters:
78
- # encrypted_data: Encrypted string
79
- def encrypted?(encrypted_data)
80
- # Simple checks first
81
- return false if (encrypted_data.length < min_encrypted_length) || (!encrypted_data.end_with?("\n"))
82
- # For now have to decrypt it fully
83
- begin
84
- decrypt(encrypted_data) ? true : false
85
- rescue
86
- false
87
- end
88
- end
89
-
90
- # Return a new random key using the configured cipher
91
- # Useful for generating new symmetric keys
92
- def random_key
93
- ::OpenSSL::Cipher::Cipher.new(@cipher).random_key
94
- end
95
-
96
- protected
97
-
98
- # Some of these methods are for future use to handle binary data, etc..
99
-
100
- # Binary encrypted data includes this magic header so that we can quickly
101
- # identify binary data versus base64 encoded data that does not have this header
102
- unless defined? MAGIC_HEADER
103
- MAGIC_HEADER = '@EnC'
104
- MAGIC_HEADER_SIZE = MAGIC_HEADER.size
105
- end
106
-
107
- # AES Symmetric Encryption of supplied string
108
- # Returns result as a binary encrypted string
109
- # Returns nil if the supplied str is nil or empty
110
- # Parameters
111
- # compress => Whether to compress the supplied string using zip before
112
- # encrypting
113
- # true | false
114
- # Default false
115
- def self.encrypt_binary(str, compress=false)
116
- return nil if str.nil? || (str.is_a?(String) && str.empty?)
117
- # Bit Layout
118
- # 15 => Compressed?
119
- # 0..14 => Version number of encryption key/algorithm currently 0
120
- flags = 0 # Same as 0b0000_0000_0000_0000
121
- # If the data is to be compressed before being encrypted, set the flag and
122
- # compress using zlib. Only compress if data is greater than 15 chars
123
- str = str.to_s unless str.is_a?(String)
124
- if compress && str.length > 15
125
- flags |= 0b1000_0000_0000_0000
126
- begin
127
- ostream = StringIO.new
128
- gz = ::Zlib::GzipWriter.new(ostream)
129
- gz.write(str)
130
- str = ostream.string
131
- ensure
132
- gz.close
133
- end
134
- end
135
- return nil unless encrypted = self.crypt(:encrypt, str)
136
- # Resulting buffer consists of:
137
- # '@EnC'
138
- # unsigned short (32 bits) in little endian format for flags above
139
- # 'actual encrypted buffer data'
140
- "#{MAGIC_HEADER}#{[flags].pack('v')}#{encrypted}"
141
- end
142
-
143
- # AES Symmetric Decryption of supplied Binary string
144
- # Returns decrypted string
145
- # Returns nil if the supplied str is nil
146
- # Returns "" if it is a string and it is empty
147
- def self.decrypt_binary(str)
148
- return str if str.nil? || (str.is_a?(String) && str.empty?)
149
- str = str.to_s unless str.is_a?(String)
150
- encrypted = if str.starts_with? MAGIC_HEADER
151
- # Remove header and extract flags
152
- header, flags = str.unpack(@@unpack ||= "A#{MAGIC_HEADER_SIZE}v")
153
- # Uncompress if data is compressed and remove header
154
- if flags & 0b1000_0000_0000_0000
155
- begin
156
- gz = ::Zlib::GzipReader.new(StringIO.new(str[MAGIC_HEADER_SIZE,-1]))
157
- gz.read
158
- ensure
159
- gz.close
160
- end
161
- else
162
- str[MAGIC_HEADER_SIZE,-1]
163
- end
164
- else
165
- ::Base64.decode64(str)
166
- end
167
- crypt(:decrypt, encrypted)
168
- end
169
-
170
- # Creates a new OpenSSL::Cipher with every call so that this call
171
- # is thread-safe
172
- def crypt(cipher_method, string) #:nodoc:
173
- openssl_cipher = ::OpenSSL::Cipher.new(self.cipher)
174
- openssl_cipher.send(cipher_method)
175
- raise "Encryption.key must be set before calling Encryption encrypt or decrypt" unless @key
176
- openssl_cipher.key = @key
177
- openssl_cipher.iv = @iv if @iv
178
- result = openssl_cipher.update(string.to_s)
179
- result << openssl_cipher.final
180
- end
181
-
182
- end
183
-
184
- end
@@ -1,262 +0,0 @@
1
- require 'base64'
2
- require 'openssl'
3
- require 'zlib'
4
- require 'yaml'
5
-
6
- module Symmetric
7
-
8
- # Encrypt using 256 Bit AES CBC symmetric key and initialization vector
9
- # The symmetric key is protected using the private key below and must
10
- # be distributed separately from the application
11
- class Encryption
12
-
13
- # Defaults
14
- @@cipher = nil
15
- @@secondary_ciphers = []
16
-
17
- # Set the Primary Symmetric Cipher to be used
18
- def self.cipher=(cipher)
19
- raise "Cipher must be similar to Symmetric::Ciphers" unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt) && cipher.respond_to?(:encrypted?)
20
- @@cipher = cipher
21
- end
22
-
23
- # Returns the Primary Symmetric Cipher being used
24
- def self.cipher
25
- @@cipher
26
- end
27
-
28
- # Set the Secondary Symmetric Ciphers Array to be used
29
- def self.secondary_ciphers=(secondary_ciphers)
30
- raise "secondary_ciphers must be a collection" unless secondary_ciphers.respond_to? :each
31
- secondary_ciphers.each do |cipher|
32
- raise "secondary_ciphers can only consist of Symmetric::Ciphers" unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt) && cipher.respond_to?(:encrypted?)
33
- end
34
- @@secondary_ciphers = secondary_ciphers
35
- end
36
-
37
- # Returns the Primary Symmetric Cipher being used
38
- def self.secondary_ciphers
39
- @@secondary_ciphers
40
- end
41
-
42
- # AES Symmetric Decryption of supplied string
43
- # Returns decrypted string
44
- # Returns nil if the supplied str is nil
45
- # Returns "" if it is a string and it is empty
46
- #
47
- # Note: If secondary ciphers are supplied in the configuration file the
48
- # first key will be used to decrypt 'str'. If it fails each cipher in the
49
- # order supplied will be tried.
50
- # It is slow to try each cipher in turn, so should be used during migrations
51
- # only
52
- #
53
- # Raises: OpenSSL::Cipher::CipherError when 'str' was not encrypted using
54
- # the supplied key and iv
55
- #
56
- def self.decrypt(str)
57
- raise "Call Symmetric::Encryption.load! or Symmetric::Encryption.cipher= prior to encrypting or decrypting data" unless @@cipher
58
- begin
59
- @@cipher.decrypt(str)
60
- rescue OpenSSL::Cipher::CipherError => exc
61
- @@secondary_ciphers.each do |cipher|
62
- begin
63
- return cipher.decrypt(str)
64
- rescue OpenSSL::Cipher::CipherError
65
- end
66
- end
67
- raise exc
68
- end
69
- end
70
-
71
- # AES Symmetric Encryption of supplied string
72
- # Returns result as a Base64 encoded string
73
- # Returns nil if the supplied str is nil
74
- # Returns "" if it is a string and it is empty
75
- def self.encrypt(str)
76
- raise "Call Symmetric::Encryption.load! or Symmetric::Encryption.cipher= prior to encrypting or decrypting data" unless @@cipher
77
- @@cipher.encrypt(str)
78
- end
79
-
80
- # Invokes decrypt
81
- # Returns decrypted String
82
- # Return nil if it fails to decrypt a String
83
- #
84
- # Useful for example when decoding passwords encrypted using a key from a
85
- # different environment. I.e. We cannot decode production passwords
86
- # in the test or development environments but still need to be able to load
87
- # YAML config files that contain encrypted development and production passwords
88
- def self.try_decrypt(str)
89
- raise "Call Symmetric::Encryption.load! or Symmetric::Encryption.cipher= prior to encrypting or decrypting data" unless @@cipher
90
- begin
91
- decrypt(str)
92
- rescue OpenSSL::Cipher::CipherError
93
- nil
94
- end
95
- end
96
-
97
- # Returns [true|false] a best effort determination as to whether the supplied
98
- # string is encrypted or not, without incurring the penalty of actually
99
- # decrypting the supplied data
100
- # Parameters:
101
- # encrypted_data: Encrypted string
102
- def self.encrypted?(encrypted_data)
103
- raise "Call Symmetric::Encryption.load! or Symmetric::Encryption.cipher= prior to encrypting or decrypting data" unless @@cipher
104
- @@cipher.encrypted?(encrypted_data)
105
- end
106
-
107
- # Load the Encryption Configuration from a YAML file
108
- # filename:
109
- # Name of file to read.
110
- # Mandatory for non-Rails apps
111
- # Default: Rails.root/config/symmetric-encryption.yml
112
- # environment:
113
- # Which environments config to load. Usually: production, development, etc.
114
- # Default: Rails.env
115
- def self.load!(filename=nil, environment=nil)
116
- config = read_config(filename, environment)
117
-
118
- # Check for hard coded key, iv and cipher
119
- if config[:key]
120
- @@cipher = Cipher.new(config)
121
- @@secondary_ciphers = []
122
- else
123
- private_rsa_key = config[:private_rsa_key]
124
- @@cipher, *@@secondary_ciphers = config[:ciphers].collect do |cipher_conf|
125
- cipher_from_encrypted_files(
126
- private_rsa_key,
127
- cipher_conf[:cipher],
128
- cipher_conf[:key_filename],
129
- cipher_conf[:iv_filename])
130
- end
131
- end
132
-
133
- true
134
- end
135
-
136
- # Future: Generate private key in config file generator
137
- #new_key = OpenSSL::PKey::RSA.generate(2048)
138
-
139
- # Generate new random symmetric keys for use with this Encryption library
140
- #
141
- # Note: Only the current Encryption key settings are used
142
- #
143
- # Creates Symmetric Key .key
144
- # and initilization vector .iv
145
- # which is encrypted with the above Public key
146
- #
147
- # Warning: Existing files will be overwritten
148
- def self.generate_symmetric_key_files(filename=nil, environment=nil)
149
- config = read_config(filename, environment)
150
- cipher_cfg = config[:ciphers].first
151
- key_filename = cipher_cfg[:key_filename]
152
- iv_filename = cipher_cfg[:iv_filename]
153
- cipher = cipher_cfg[:cipher]
154
-
155
- raise "The configuration file must contain a 'private_rsa_key' parameter to generate symmetric keys" unless config[:private_rsa_key]
156
- rsa_key = OpenSSL::PKey::RSA.new(config[:private_rsa_key])
157
-
158
- # Generate a new Symmetric Key pair
159
- key_pair = Symmetric::Cipher.random_key_pair(cipher || 'aes-256-cbc', !iv_filename.nil?)
160
-
161
- # Save symmetric key after encrypting it with the private RSA key, backing up existing files if present
162
- File.rename(key_filename, "#{key_filename}.#{Time.now.to_i}") if File.exist?(key_filename)
163
- File.open(key_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:key]) ) }
164
-
165
- if iv_filename
166
- File.rename(iv_filename, "#{iv_filename}.#{Time.now.to_i}") if File.exist?(iv_filename)
167
- File.open(iv_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:iv]) ) }
168
- end
169
- puts("Generated new Symmetric Key for encryption. Please copy #{key_filename} and #{iv_filename} to the other web servers in #{environment}.")
170
- end
171
-
172
- # Generate a 22 character random password
173
- def self.random_password
174
- Base64.encode64(OpenSSL::Cipher.new('aes-128-cbc').random_key)[0..-4]
175
- end
176
-
177
- protected
178
-
179
- # Returns the Encryption Configuration
180
- #
181
- # Read the configuration from the YAML file and return in the latest format
182
- #
183
- # filename:
184
- # Name of file to read.
185
- # Mandatory for non-Rails apps
186
- # Default: Rails.root/config/symmetric-encryption.yml
187
- # environment:
188
- # Which environments config to load. Usually: production, development, etc.
189
- def self.read_config(filename=nil, environment=nil)
190
- config = YAML.load_file(filename || File.join(Rails.root, "config", "symmetric-encryption.yml"))[environment || Rails.env]
191
-
192
- # Default cipher
193
- default_cipher = config['cipher'] || 'aes-256-cbc'
194
- cfg = {}
195
-
196
- # Hard coded symmetric_key? - Dev / Testing use only!
197
- if symmetric_key = (config['key'] || config['symmetric_key'])
198
- raise "Symmetric::Encryption Cannot hard code Production encryption keys in #{filename}" if (environment || Rails.env) == 'production'
199
- cfg[:key] = symmetric_key
200
- cfg[:iv] = config['iv'] || config['symmetric_iv']
201
- cfg[:cipher] = default_cipher
202
-
203
- elsif ciphers = config['ciphers']
204
- raise "Missing mandatory config parameter 'private_rsa_key'" unless cfg[:private_rsa_key] = config['private_rsa_key']
205
-
206
- cfg[:ciphers] = ciphers.collect do |cipher_cfg|
207
- key_filename = cipher_cfg['key_filename'] || cipher_cfg['symmetric_key_filename']
208
- raise "Missing mandatory 'key_filename' for environment:#{environment} in #{filename}" unless key_filename
209
- iv_filename = cipher_cfg['iv_filename'] || cipher_cfg['symmetric_iv_filename']
210
- {
211
- :cipher => cipher_cfg['cipher'] || default_cipher,
212
- :key_filename => key_filename,
213
- :iv_filename => iv_filename,
214
- }
215
- end
216
-
217
- else
218
- # Migrate old format config
219
- raise "Missing mandatory config parameter 'private_rsa_key'" unless cfg[:private_rsa_key] = config['private_rsa_key']
220
- cfg[:ciphers] = [ {
221
- :cipher => default_cipher,
222
- :key_filename => config['symmetric_key_filename'],
223
- :iv_filename => config['symmetric_iv_filename'],
224
- } ]
225
- end
226
-
227
- cfg
228
- end
229
-
230
- # Returns an instance of Symmetric::Cipher initialized from keys
231
- # stored in files
232
- #
233
- # Raises an Exception on failure
234
- #
235
- # Parameters:
236
- # cipher
237
- # Encryption cipher for the symmetric encryption key
238
- # private_key
239
- # Key used to unlock file containing the actual symmetric key
240
- # key_filename
241
- # Name of file containing symmetric key encrypted using the public
242
- # key matching the supplied private_key
243
- # iv_filename
244
- # Optional. Name of file containing symmetric key initialization vector
245
- # encrypted using the public key matching the supplied private_key
246
- def self.cipher_from_encrypted_files(private_rsa_key, cipher, key_filename, iv_filename = nil)
247
- # Load Encrypted Symmetric keys
248
- encrypted_key = File.read(key_filename)
249
- encrypted_iv = File.read(iv_filename) if iv_filename
250
-
251
- # Decrypt Symmetric Keys
252
- rsa = OpenSSL::PKey::RSA.new(private_rsa_key)
253
- iv = rsa.private_decrypt(encrypted_iv) if iv_filename
254
- Cipher.new(
255
- :key => rsa.private_decrypt(encrypted_key),
256
- :iv => iv,
257
- :cipher => cipher
258
- )
259
- end
260
-
261
- end
262
- end