symmetric-encryption 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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