symmetric-encryption 2.2.0 → 3.0.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.
- 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
|