symmetric-encryption 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +39 -1
- data/Rakefile +4 -16
- data/lib/symmetric_encryption/cipher.rb +189 -103
- data/lib/symmetric_encryption/reader.rb +16 -5
- data/lib/symmetric_encryption/symmetric_encryption.rb +203 -116
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +1 -1
- data/test/attr_encrypted_test.rb +2 -2
- data/test/cipher_test.rb +30 -10
- data/test/config/symmetric-encryption.yml +41 -11
- data/test/field_encrypted_test.rb +2 -2
- data/test/reader_test.rb +23 -14
- data/test/symmetric_encryption_test.rb +58 -26
- data/test/test_db.sqlite3 +0 -0
- data/test/writer_test.rb +4 -0
- metadata +24 -16
- data/Gemfile +0 -19
- data/Gemfile.lock +0 -61
- data/nbproject/private/config.properties +0 -0
- data/nbproject/private/private.properties +0 -1
- data/nbproject/private/private.xml +0 -4
- data/nbproject/private/rake-d.txt +0 -4
- data/nbproject/project.properties +0 -9
- data/nbproject/project.xml +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ef6c56bc1e2a9a0231a0f98f37b4cd4ca485842
|
4
|
+
data.tar.gz: 52318d2b70e96ecfc6067e35d8c7b3ca0a339933
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 461b4904ad5b16293b4edb7f760486bbaf30a548a04debd3418c04a829d87e03f40db7a665e318e43435d140ed4fcbc07d45953158a23ac9e26c3dd725f894af
|
7
|
+
data.tar.gz: 100babba3863c0a0b4abcdb800e6b6afe6d56ee66e43c85ff9b5cdb957f931d7660ac462e73014c07b4754df927fb264f0b5d2c77b71da0e2ba88f623c6b68b3
|
data/README.md
CHANGED
@@ -15,6 +15,30 @@ and consistent way
|
|
15
15
|
Symmetric Encryption uses OpenSSL to encrypt and decrypt data, and can therefore
|
16
16
|
expose all the encryption algorithms supported by OpenSSL.
|
17
17
|
|
18
|
+
## Upgrading from earlier versions to SymmetricEncryption V3
|
19
|
+
|
20
|
+
In version 3 of SymmetricEncryption, the following changes have been made that
|
21
|
+
may have backward compatibility issues:
|
22
|
+
|
23
|
+
* SymmetricEncryption.decrypt no longer rotates through all the decryption keys
|
24
|
+
when previous ciphers fail to decrypt the encrypted string.
|
25
|
+
In a very small, yet significant number of cases it was possible to decrypt data
|
26
|
+
using the incorrect key. Clearly the data returned was garbage, but it still
|
27
|
+
returned a string of data instead of throwing an exception.
|
28
|
+
See SymmetricEncryption.select_cipher to supply your own custom logic to
|
29
|
+
determine the correct cipher to use when the encrypted string does not have a
|
30
|
+
header and multiple ciphers are defined.
|
31
|
+
|
32
|
+
* Configuration file format prior to V1 is no longer supported
|
33
|
+
|
34
|
+
* New configuration option has been added to support setting encryption keys
|
35
|
+
from environment variables
|
36
|
+
|
37
|
+
* Cipher.parse_magic_header! now returns a Struct instead of an Array
|
38
|
+
|
39
|
+
* New config options :encrypted_key and :encrypted_iv to support setting
|
40
|
+
the encryption key in environment variables
|
41
|
+
|
18
42
|
## Security
|
19
43
|
|
20
44
|
Many solutions that encrypt data require the encryption keys to be stored in the
|
@@ -50,6 +74,12 @@ From a security perspective it is important then to properly secure the system s
|
|
50
74
|
no hacker can switch to and run as the rails user and thereby gain access to the
|
51
75
|
encryption and decryption capabilities
|
52
76
|
|
77
|
+
It is not necessary to encrypt the IV (initialization vector), and it can be placed
|
78
|
+
directly in the configuration file. The encryption key must be kept secure and
|
79
|
+
must never be placed in the configuration file or other Rails source file in production.
|
80
|
+
The IV should be generated using the rails generator described below to ensure
|
81
|
+
it is a truly random key from the key space.
|
82
|
+
|
53
83
|
## Limitations
|
54
84
|
|
55
85
|
By default symmetric encryption uses the same initialization vector (IV) and
|
@@ -78,7 +108,7 @@ option will result in different encrypted output every time it is encrypted.
|
|
78
108
|
|
79
109
|
* Encryption of passwords in configuration files
|
80
110
|
* Encryption of ActiveRecord model attributes by prefixing attributes / column
|
81
|
-
names with encrypted_
|
111
|
+
names with encrypted_
|
82
112
|
* Encryption of Mongoid model fields by adding :encrypted option to field
|
83
113
|
definitions
|
84
114
|
* Externalization of symmetric encryption keys so that they are not in the
|
@@ -98,6 +128,14 @@ names with encrypted_
|
|
98
128
|
* For maximum security supports fully random keys and initialization vectors
|
99
129
|
extracted from the entire encryption key space
|
100
130
|
|
131
|
+
## Recommendations
|
132
|
+
|
133
|
+
* Add the encryption header to all encrypted strings.
|
134
|
+
See the _always_add_header_ option in the configuration file.
|
135
|
+
|
136
|
+
* Set :random_iv => true for all ActiveRecord attributes and Mongoid fields
|
137
|
+
which are not used in indexes and will not be used as part of a query.
|
138
|
+
|
101
139
|
## Examples
|
102
140
|
|
103
141
|
### Encryption Example
|
data/Rakefile
CHANGED
@@ -5,26 +5,11 @@ require 'rubygems'
|
|
5
5
|
require 'rubygems/package'
|
6
6
|
require 'rake/clean'
|
7
7
|
require 'rake/testtask'
|
8
|
-
require 'date'
|
9
8
|
require 'symmetric_encryption/version'
|
10
9
|
|
11
10
|
desc "Build gem"
|
12
11
|
task :gem do |t|
|
13
|
-
|
14
|
-
s.name = 'symmetric-encryption'
|
15
|
-
s.version = SymmetricEncryption::VERSION
|
16
|
-
s.platform = Gem::Platform::RUBY
|
17
|
-
s.authors = ['Reid Morrison']
|
18
|
-
s.email = ['reidmo@gmail.com']
|
19
|
-
s.homepage = 'https://github.com/ClarityServices/symmetric-encryption'
|
20
|
-
s.date = Date.today.to_s
|
21
|
-
s.summary = "Symmetric Encryption for Ruby, and Ruby on Rails"
|
22
|
-
s.description = "SymmetricEncryption supports encrypting ActiveRecord data, Mongoid data, passwords in configuration files, encrypting and decrypting of large files through streaming"
|
23
|
-
s.files = FileList["./**/*"].exclude(/.gem$/, /.log$/,/^nbproject/).map{|f| f.sub(/^\.\//, '')}
|
24
|
-
s.license = "Apache License V2.0"
|
25
|
-
s.has_rdoc = true
|
26
|
-
end
|
27
|
-
Gem::Package.build gemspec
|
12
|
+
Gem::Package.build(Gem::Specification.load('symmetric-encryption.gemspec'))
|
28
13
|
end
|
29
14
|
|
30
15
|
desc "Run Test Suite"
|
@@ -34,5 +19,8 @@ task :test do
|
|
34
19
|
t.verbose = true
|
35
20
|
end
|
36
21
|
|
22
|
+
# For mongoid
|
23
|
+
ENV['RACK_ENV'] = 'test'
|
24
|
+
|
37
25
|
Rake::Task['functional'].invoke
|
38
26
|
end
|
@@ -7,8 +7,8 @@ module SymmetricEncryption
|
|
7
7
|
# threads at the same time without needing an instance of Cipher per thread
|
8
8
|
class Cipher
|
9
9
|
# Cipher to use for encryption and decryption
|
10
|
-
attr_reader :cipher_name, :version
|
11
|
-
attr_accessor :encoding
|
10
|
+
attr_reader :cipher_name, :version, :iv
|
11
|
+
attr_accessor :encoding, :always_add_header
|
12
12
|
|
13
13
|
# Available encodings
|
14
14
|
ENCODINGS = [:none, :base64, :base64strict, :base16]
|
@@ -16,18 +16,29 @@ module SymmetricEncryption
|
|
16
16
|
# Backward compatibility
|
17
17
|
alias_method :cipher, :cipher_name
|
18
18
|
|
19
|
+
# Defines the Header Structure returned when parsing the header
|
20
|
+
HeaderStruct = Struct.new(
|
21
|
+
:compressed, # [true|false] Whether the data is compressed, if supplied in the header
|
22
|
+
:binary, # [true|false] Whether the data is binary, if supplied in the header
|
23
|
+
:iv, # [String] IV used to encrypt the data, if supplied in the header
|
24
|
+
:key, # [String] Key used to encrypt the data, if supplied in the header
|
25
|
+
:cipher_name, # [String] Name of the cipher used, if supplied in the header
|
26
|
+
:version, # [Integer] Version of the cipher used, if supplied in the header
|
27
|
+
:decryption_cipher, # [SymmetricEncryption::Cipher] Cipher matching the header, or SymmetricEncryption.cipher(default_version)
|
28
|
+
)
|
29
|
+
|
19
30
|
# Generate a new Symmetric Key pair
|
20
31
|
#
|
21
32
|
# Returns a hash containing a new random symmetric_key pair
|
22
33
|
# consisting of a :key and :iv.
|
23
34
|
# The cipher_name is also included for compatibility with the Cipher initializer
|
24
|
-
def self.random_key_pair(cipher_name = 'aes-256-cbc'
|
35
|
+
def self.random_key_pair(cipher_name = 'aes-256-cbc')
|
25
36
|
openssl_cipher = ::OpenSSL::Cipher.new(cipher_name)
|
26
37
|
openssl_cipher.encrypt
|
27
38
|
|
28
39
|
{
|
29
40
|
:key => openssl_cipher.random_key,
|
30
|
-
:iv =>
|
41
|
+
:iv => openssl_cipher.random_iv,
|
31
42
|
:cipher_name => cipher_name
|
32
43
|
}
|
33
44
|
end
|
@@ -65,18 +76,34 @@ module SymmetricEncryption
|
|
65
76
|
# Optional. The version number of this encryption key
|
66
77
|
# Used by SymmetricEncryption to select the correct key when decrypting data
|
67
78
|
# Maximum value: 255
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
79
|
+
#
|
80
|
+
# :always_add_header [true|false]
|
81
|
+
# Whether to always include the header when encrypting data.
|
82
|
+
# ** Highly recommended to set this value to true **
|
83
|
+
# Increases the length of the encrypted data by a few bytes, but makes
|
84
|
+
# migration to a new key trivial
|
85
|
+
# Default: false
|
86
|
+
# Recommended: true
|
87
|
+
#
|
88
|
+
def initialize(params={})
|
89
|
+
parms = params.dup
|
90
|
+
@key = parms.delete(:key)
|
91
|
+
@iv = parms.delete(:iv)
|
92
|
+
@cipher_name = parms.delete(:cipher_name) || parms.delete(:cipher) || 'aes-256-cbc'
|
93
|
+
@version = parms.delete(:version)
|
94
|
+
@always_add_header = parms.delete(:always_add_header) || false
|
95
|
+
@encoding = (parms.delete(:encoding) || :base64).to_sym
|
96
|
+
|
97
|
+
raise "Missing mandatory parameter :key" unless @key
|
98
|
+
raise "Invalid Encoding: #{@encoding}" unless ENCODINGS.include?(@encoding)
|
99
|
+
raise "Cipher version has a valid rage of 0 to 255. #{@version} is too high, or negative" if (@version.to_i > 255) || (@version.to_i < 0)
|
100
|
+
parms.each_pair {|k,v| warn "SymmetricEncryption::Cipher Ignoring unknown option #{k.inspect} = #{v.inspect}"}
|
77
101
|
end
|
78
102
|
|
79
|
-
#
|
103
|
+
# Encrypt and then encode a binary or UTF-8 string
|
104
|
+
#
|
105
|
+
# Returns data encrypted and then encoded according to the encoding setting
|
106
|
+
# of this cipher
|
80
107
|
# Returns nil if str is nil
|
81
108
|
# Returns "" str is empty
|
82
109
|
#
|
@@ -105,7 +132,7 @@ module SymmetricEncryption
|
|
105
132
|
# compress [true|false]
|
106
133
|
# Whether to compress str before encryption
|
107
134
|
# Should only be used for large strings since compression overhead and
|
108
|
-
# the overhead of adding the
|
135
|
+
# the overhead of adding the encryption header may exceed any benefits of
|
109
136
|
# compression
|
110
137
|
# Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
|
111
138
|
# Default: false
|
@@ -117,40 +144,36 @@ module SymmetricEncryption
|
|
117
144
|
self.encode(encrypted)
|
118
145
|
end
|
119
146
|
|
120
|
-
#
|
147
|
+
# Decode and Decrypt string
|
148
|
+
# Returns a decrypted string after decoding it first according to the
|
149
|
+
# encoding setting of this cipher
|
150
|
+
# Returns nil if encrypted_string is nil
|
151
|
+
# Returns '' if encrypted_string == ''
|
121
152
|
#
|
122
|
-
#
|
153
|
+
# Parameters
|
154
|
+
# encrypted_string [String]
|
155
|
+
# Binary encrypted string to decrypt
|
123
156
|
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
crypt(:decrypt, decoded)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# Return a new random key using the configured cipher_name
|
146
|
-
# Useful for generating new symmetric keys
|
147
|
-
def random_key
|
148
|
-
::OpenSSL::Cipher::Cipher.new(@cipher_name).random_key
|
149
|
-
end
|
157
|
+
# header [HeaderStruct]
|
158
|
+
# Optional header for the supplied encrypted_string
|
159
|
+
#
|
160
|
+
# binary [true|false]
|
161
|
+
# If no header is supplied then determines whether the string returned
|
162
|
+
# is binary or UTF8
|
163
|
+
#
|
164
|
+
# Reads the 'magic' header if present for key, iv, cipher_name and compression
|
165
|
+
#
|
166
|
+
# encrypted_string must be in raw binary form when calling this method
|
167
|
+
#
|
168
|
+
# Creates a new OpenSSL::Cipher with every call so that this call
|
169
|
+
# is thread-safe and can be called concurrently by multiple threads with
|
170
|
+
# the same instance of Cipher
|
171
|
+
def decrypt(str)
|
172
|
+
decoded = self.decode(str)
|
173
|
+
return unless decoded
|
150
174
|
|
151
|
-
|
152
|
-
|
153
|
-
::OpenSSL::Cipher::Cipher.new(@cipher_name).block_size
|
175
|
+
return decoded if decoded.empty?
|
176
|
+
binary_decrypt(decoded).force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
154
177
|
end
|
155
178
|
|
156
179
|
# Returns UTF8 encoded string after encoding the supplied Binary string
|
@@ -161,16 +184,22 @@ module SymmetricEncryption
|
|
161
184
|
#
|
162
185
|
# Returned string is UTF8 encoded except for encoding :none
|
163
186
|
def encode(binary_string)
|
164
|
-
return
|
187
|
+
return binary_string if binary_string.nil? || (binary_string == '')
|
165
188
|
|
166
189
|
# Now encode data based on encoding setting
|
167
190
|
case encoding
|
168
191
|
when :base64
|
169
|
-
::Base64.encode64(binary_string)
|
192
|
+
encoded_string = ::Base64.encode64(binary_string)
|
193
|
+
# Support Ruby 1.9 encoding
|
194
|
+
defined?(Encoding) ? encoded_string.force_encoding(SymmetricEncryption::UTF8_ENCODING) : encoded_string
|
170
195
|
when :base64strict
|
171
|
-
::Base64.encode64(binary_string).gsub(/\n/, '')
|
196
|
+
encoded_string = ::Base64.encode64(binary_string).gsub(/\n/, '')
|
197
|
+
# Support Ruby 1.9 encoding
|
198
|
+
defined?(Encoding) ? encoded_string.force_encoding(SymmetricEncryption::UTF8_ENCODING) : encoded_string
|
172
199
|
when :base16
|
173
|
-
binary_string.to_s.unpack('H*').first
|
200
|
+
encoded_string = binary_string.to_s.unpack('H*').first
|
201
|
+
# Support Ruby 1.9 encoding
|
202
|
+
defined?(Encoding) ? encoded_string.force_encoding(SymmetricEncryption::UTF8_ENCODING) : encoded_string
|
174
203
|
else
|
175
204
|
binary_string
|
176
205
|
end
|
@@ -181,42 +210,53 @@ module SymmetricEncryption
|
|
181
210
|
#
|
182
211
|
# Returned string is Binary encoded
|
183
212
|
def decode(encoded_string)
|
184
|
-
return
|
213
|
+
return encoded_string if encoded_string.nil? || (encoded_string == '')
|
185
214
|
|
186
215
|
case encoding
|
187
216
|
when :base64, :base64strict
|
188
|
-
::Base64.decode64(encoded_string)
|
217
|
+
decoded_string = ::Base64.decode64(encoded_string)
|
218
|
+
# Support Ruby 1.9 encoding
|
219
|
+
defined?(Encoding) ? decoded_string.force_encoding(SymmetricEncryption::BINARY_ENCODING) : decoded_string
|
189
220
|
when :base16
|
190
|
-
[encoded_string].pack('H*')
|
221
|
+
decoded_string = [encoded_string].pack('H*')
|
222
|
+
# Support Ruby 1.9 encoding
|
223
|
+
defined?(Encoding) ? decoded_string.force_encoding(SymmetricEncryption::BINARY_ENCODING) : decoded_string
|
191
224
|
else
|
192
225
|
encoded_string
|
193
226
|
end
|
194
227
|
end
|
195
228
|
|
196
|
-
#
|
197
|
-
#
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
#
|
229
|
+
# Return a new random key using the configured cipher_name
|
230
|
+
# Useful for generating new symmetric keys
|
231
|
+
def random_key
|
232
|
+
::OpenSSL::Cipher::Cipher.new(@cipher_name).random_key
|
233
|
+
end
|
234
|
+
|
235
|
+
# Returns the block size for the configured cipher_name
|
236
|
+
def block_size
|
237
|
+
::OpenSSL::Cipher::Cipher.new(@cipher_name).block_size
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns whether the supplied buffer starts with a symmetric_encryption header
|
241
|
+
# Note: The encoding of the supplied buffer is forced to binary if not already binary
|
242
|
+
def self.has_header?(buffer)
|
243
|
+
return false if buffer.nil? || (buffer == '')
|
244
|
+
buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING) if buffer.respond_to?(:force_encoding)
|
245
|
+
buffer.start_with?(MAGIC_HEADER)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns HeaderStruct of the header parsed from the supplied string
|
249
|
+
# Returns nil if no header is present
|
203
250
|
#
|
204
|
-
# The supplied buffer will be updated directly and
|
205
|
-
#
|
251
|
+
# The supplied buffer will be updated directly and its header will be
|
252
|
+
# stripped if present
|
206
253
|
#
|
207
254
|
# Parameters
|
208
255
|
# buffer
|
209
|
-
# String to extract the header from
|
210
|
-
#
|
211
|
-
# default_version
|
212
|
-
# If no header is present, this is the default value for the version
|
213
|
-
# of the cipher to use
|
256
|
+
# String to extract the header from
|
214
257
|
#
|
215
|
-
|
216
|
-
|
217
|
-
def self.parse_magic_header!(buffer, default_version=nil, default_compressed=false)
|
218
|
-
buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING) if buffer
|
219
|
-
return [default_compressed, nil, nil, nil, nil, SymmetricEncryption.cipher(default_version)] unless buffer && buffer.start_with?(MAGIC_HEADER)
|
258
|
+
def self.parse_header!(buffer)
|
259
|
+
return unless has_header?(buffer)
|
220
260
|
|
221
261
|
# Header includes magic header and version byte
|
222
262
|
# Remove header and extract flags
|
@@ -225,6 +265,7 @@ module SymmetricEncryption
|
|
225
265
|
include_iv = (flags & 0b0100_0000_0000_0000) != 0
|
226
266
|
include_key = (flags & 0b0010_0000_0000_0000) != 0
|
227
267
|
include_cipher= (flags & 0b0001_0000_0000_0000) != 0
|
268
|
+
binary = (flags & 0b0000_1000_0000_0000) != 0
|
228
269
|
# Version of the key to use to decrypt the key if present,
|
229
270
|
# otherwise to decrypt the data following the header
|
230
271
|
version = flags & 0b0000_0000_1111_1111
|
@@ -238,14 +279,14 @@ module SymmetricEncryption
|
|
238
279
|
end
|
239
280
|
if include_key
|
240
281
|
len = buffer.slice!(0..1).unpack('v').first
|
241
|
-
key = decryption_cipher.binary_decrypt(buffer.slice!(0..len-1))
|
282
|
+
key = decryption_cipher.binary_decrypt(buffer.slice!(0..len-1), header=false, binary=true)
|
242
283
|
end
|
243
284
|
if include_cipher
|
244
285
|
len = buffer.slice!(0..1).unpack('v').first
|
245
286
|
cipher_name = buffer.slice!(0..len-1)
|
246
287
|
end
|
247
288
|
|
248
|
-
|
289
|
+
HeaderStruct.new(compressed, binary, iv, key, cipher_name, version, decryption_cipher)
|
249
290
|
end
|
250
291
|
|
251
292
|
# Returns a magic header for this cipher instance that can be placed at
|
@@ -269,16 +310,15 @@ module SymmetricEncryption
|
|
269
310
|
# Includes the cipher_name used. For example 'aes-256-cbc'
|
270
311
|
# The cipher_name string to to put in the header
|
271
312
|
# Default: nil : Exclude cipher_name name from header
|
272
|
-
|
313
|
+
#
|
314
|
+
# binary
|
315
|
+
# Whether the data being encrypted is binary.
|
316
|
+
# When the header is read, it sets the encoding of the string returned to Binary
|
317
|
+
def self.build_header(version, compressed=false, iv=nil, key=nil, cipher_name=nil, binary=false)
|
273
318
|
# Ruby V2 named parameters would be perfect here
|
274
319
|
|
275
|
-
#
|
276
|
-
flags = version || 0 # Same as 0b0000_0000_0000_0000
|
277
|
-
|
278
|
-
# Replace version with global cipher that will be used to encrypt the random key
|
279
|
-
if iv || key
|
280
|
-
flags = (SymmetricEncryption.cipher.version || 0)
|
281
|
-
end
|
320
|
+
# Version number of supplied encryption key, or use the global cipher version if none was supplied
|
321
|
+
flags = iv || key ? (SymmetricEncryption.cipher.version || 0) : (version || 0) # Same as 0b0000_0000_0000_0000
|
282
322
|
|
283
323
|
# If the data is to be compressed before being encrypted, set the
|
284
324
|
# compressed bit in the flags word
|
@@ -286,13 +326,14 @@ module SymmetricEncryption
|
|
286
326
|
flags |= 0b0100_0000_0000_0000 if iv
|
287
327
|
flags |= 0b0010_0000_0000_0000 if key
|
288
328
|
flags |= 0b0001_0000_0000_0000 if cipher_name
|
329
|
+
flags |= 0b0000_1000_0000_0000 if binary
|
289
330
|
header = "#{MAGIC_HEADER}#{[flags].pack('v')}".force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
290
331
|
if iv
|
291
332
|
header << [iv.length].pack('v')
|
292
333
|
header << iv
|
293
334
|
end
|
294
335
|
if key
|
295
|
-
encrypted = SymmetricEncryption.cipher.binary_encrypt(key, false, false)
|
336
|
+
encrypted = SymmetricEncryption.cipher.binary_encrypt(key, false, false, false)
|
296
337
|
header << [encrypted.length].pack('v').force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
297
338
|
header << encrypted
|
298
339
|
end
|
@@ -307,21 +348,33 @@ module SymmetricEncryption
|
|
307
348
|
#
|
308
349
|
# Returns a Binary encrypted string without applying any Base64, or other encoding
|
309
350
|
#
|
310
|
-
#
|
351
|
+
# add_header [nil|true|false]
|
352
|
+
# Whether to add a header to the encrypted string
|
353
|
+
# If not supplied it defaults to true if always_add_header || random_iv || compress
|
354
|
+
# Default: nil
|
311
355
|
#
|
312
356
|
# Creates a new OpenSSL::Cipher with every call so that this call
|
313
357
|
# is thread-safe
|
314
358
|
#
|
315
359
|
# See #encrypt to encrypt and encode the result as a string
|
316
|
-
def binary_encrypt(
|
360
|
+
def binary_encrypt(str, random_iv=false, compress=false, add_header=nil)
|
361
|
+
return if str.nil?
|
362
|
+
string = str.to_s
|
363
|
+
return string if string.empty?
|
364
|
+
|
365
|
+
# Creates a new OpenSSL::Cipher with every call so that this call
|
366
|
+
# is thread-safe
|
317
367
|
openssl_cipher = ::OpenSSL::Cipher.new(self.cipher_name)
|
318
368
|
openssl_cipher.encrypt
|
319
369
|
openssl_cipher.key = @key
|
320
|
-
|
370
|
+
add_header = always_add_header || random_iv || compress if add_header.nil?
|
371
|
+
result = if add_header
|
321
372
|
# Random iv and compress both add the magic header
|
322
373
|
iv = random_iv ? openssl_cipher.random_iv : @iv
|
323
374
|
openssl_cipher.iv = iv if iv
|
324
|
-
|
375
|
+
# Set the binary indicator on the header if string is Binary Encoded
|
376
|
+
binary = (string.encoding == SymmetricEncryption::BINARY_ENCODING)
|
377
|
+
self.class.build_header(version, compress, random_iv ? iv : nil, binary) +
|
325
378
|
openssl_cipher.update(compress ? Zlib::Deflate.deflate(string) : string)
|
326
379
|
else
|
327
380
|
openssl_cipher.iv = @iv if @iv
|
@@ -331,48 +384,81 @@ module SymmetricEncryption
|
|
331
384
|
end
|
332
385
|
|
333
386
|
# Advanced use only
|
387
|
+
# See #decrypt to decrypt encoded strings
|
334
388
|
#
|
335
389
|
# Returns a Binary decrypted string without decoding the string first
|
336
390
|
#
|
391
|
+
# Decryption of supplied string
|
392
|
+
# Returns the decrypted string
|
393
|
+
# Returns nil if encrypted_string is nil
|
394
|
+
# Returns '' if encrypted_string == ''
|
395
|
+
#
|
396
|
+
# Parameters
|
397
|
+
# encrypted_string [String]
|
398
|
+
# Binary encrypted string to decrypt
|
399
|
+
#
|
400
|
+
# header [HeaderStruct]
|
401
|
+
# Optional header for the supplied encrypted_string
|
402
|
+
#
|
403
|
+
# binary [true|false]
|
404
|
+
# If no header is supplied then determines whether the string returned
|
405
|
+
# is binary or UTF8
|
406
|
+
#
|
337
407
|
# Reads the 'magic' header if present for key, iv, cipher_name and compression
|
338
408
|
#
|
339
409
|
# encrypted_string must be in raw binary form when calling this method
|
340
410
|
#
|
341
411
|
# Creates a new OpenSSL::Cipher with every call so that this call
|
342
|
-
# is thread-safe
|
343
|
-
#
|
344
|
-
#
|
345
|
-
|
412
|
+
# is thread-safe and can be called concurrently by multiple threads with
|
413
|
+
# the same instance of Cipher
|
414
|
+
#
|
415
|
+
# Note:
|
416
|
+
# When a string is encrypted and the header is used, its decrypted form
|
417
|
+
# is automatically set to the same UTF-8 or Binary encoding
|
418
|
+
def binary_decrypt(encrypted_string, header=nil, binary=false)
|
419
|
+
return if encrypted_string.nil?
|
346
420
|
str = encrypted_string.to_s
|
347
|
-
if str.
|
421
|
+
str.force_encoding(SymmetricEncryption::BINARY_ENCODING) if str.respond_to?(:force_encoding)
|
422
|
+
return str if str.empty?
|
423
|
+
|
424
|
+
decrypted_string = if header || self.class.has_header?(str)
|
348
425
|
str = str.dup
|
349
|
-
|
350
|
-
|
426
|
+
header ||= self.class.parse_header!(str)
|
427
|
+
binary = header.binary
|
428
|
+
|
429
|
+
openssl_cipher = ::OpenSSL::Cipher.new(header.cipher_name || self.cipher_name)
|
351
430
|
openssl_cipher.decrypt
|
352
|
-
openssl_cipher.key = key || @key
|
353
|
-
iv
|
431
|
+
openssl_cipher.key = header.key || @key
|
432
|
+
iv = header.iv || @iv
|
354
433
|
openssl_cipher.iv = iv if iv
|
355
434
|
result = openssl_cipher.update(str)
|
356
435
|
result << openssl_cipher.final
|
357
|
-
compressed ? Zlib::Inflate.inflate(result) : result
|
436
|
+
header.compressed ? Zlib::Inflate.inflate(result) : result
|
358
437
|
else
|
359
438
|
openssl_cipher = ::OpenSSL::Cipher.new(self.cipher_name)
|
360
439
|
openssl_cipher.decrypt
|
361
440
|
openssl_cipher.key = @key
|
362
441
|
openssl_cipher.iv = @iv if @iv
|
363
|
-
result = openssl_cipher.update(
|
442
|
+
result = openssl_cipher.update(str)
|
364
443
|
result << openssl_cipher.final
|
365
444
|
end
|
445
|
+
|
446
|
+
# Support Ruby 1.9 and above Encoding
|
447
|
+
if defined?(Encoding)
|
448
|
+
# Sets the encoding of the result string to UTF8 or BINARY based on the binary header
|
449
|
+
binary ? decrypted_string.force_encoding(SymmetricEncryption::BINARY_ENCODING) : decrypted_string.force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
450
|
+
else
|
451
|
+
decrypted_string
|
452
|
+
end
|
366
453
|
end
|
367
454
|
|
368
|
-
# Returns [String] object represented as a string
|
369
|
-
# Excluding the key and iv
|
455
|
+
# Returns [String] object represented as a string, filtering out the key
|
370
456
|
def inspect
|
371
|
-
|
457
|
+
"#<#{self.class}:0x#{self.__id__.to_s(16)} @key=\"[FILTERED]\" @iv=#{iv.inspect} @cipher_name=#{cipher_name.inspect}, @version=#{version.inspect}, @encoding=#{encoding.inspect}"
|
372
458
|
end
|
373
459
|
|
374
460
|
private
|
375
461
|
|
376
|
-
attr_reader :key
|
462
|
+
attr_reader :key
|
377
463
|
end
|
378
|
-
end
|
464
|
+
end
|
@@ -311,20 +311,31 @@ module SymmetricEncryption
|
|
311
311
|
|
312
312
|
# Read the header from the file if present
|
313
313
|
def read_header
|
314
|
-
@compressed = nil
|
315
314
|
@pos = 0
|
316
315
|
|
317
316
|
# Read first block and check for the header
|
318
317
|
buf = @ios.read(@buffer_size)
|
319
318
|
|
320
319
|
# Use cipher specified in header, or global cipher if it has no header
|
321
|
-
|
322
|
-
|
320
|
+
iv, key, cipher_name, decryption_cipher = nil
|
321
|
+
if header = SymmetricEncryption::Cipher.parse_header!(buf)
|
322
|
+
@header_present = true
|
323
|
+
@compressed = header.compressed
|
324
|
+
decryption_cipher = header.decryption_cipher
|
325
|
+
cipher_name = header.cipher_name || decryption_cipher.cipher_name
|
326
|
+
key = header.key
|
327
|
+
iv = header.iv
|
328
|
+
else
|
329
|
+
@header_present = false
|
330
|
+
@compressed = nil
|
331
|
+
decryption_cipher = SymmetricEncryption.cipher(@version)
|
332
|
+
cipher_name = decryption_cipher.cipher_name
|
333
|
+
end
|
323
334
|
|
324
|
-
@stream_cipher = ::OpenSSL::Cipher.new(cipher_name
|
335
|
+
@stream_cipher = ::OpenSSL::Cipher.new(cipher_name)
|
325
336
|
@stream_cipher.decrypt
|
326
337
|
@stream_cipher.key = key || decryption_cipher.send(:key)
|
327
|
-
@stream_cipher.iv = iv || decryption_cipher.
|
338
|
+
@stream_cipher.iv = iv || decryption_cipher.iv
|
328
339
|
|
329
340
|
# First call to #update should return an empty string anyway
|
330
341
|
if buf && buf.length > 0
|