symmetric-encryption 1.1.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 94fbb4339aaed37c7526621cf7dd768aaae56fb7
4
- data.tar.gz: bc40aa329637465ca0747be1efac9a29a9154007
3
+ metadata.gz: 32af889e864031974d67cc49ed09fca4fc6a9ceb
4
+ data.tar.gz: be8ef6b6193bd44dbf4b3c760b31e9223f0e4ce1
5
5
  SHA512:
6
- metadata.gz: 04f23adcb3fbdf114a28a7460c9e120aa33fbb3d5897b2adf7700b559cf4fa78ae8cebb7b972383816c9a4b1f5202fead4abb5d87e1335cc65cdb6020169e6bc
7
- data.tar.gz: bce09dff95ff11023aab38965f02a12f1ae6c49069256c54963b7db8bc5dd0889680c878d4b906b1f603599dbf9431d4239c987004d13ee00c5751420793f444
6
+ metadata.gz: cfdba0c992f3f82765ab3592a386cdceb9eb81265d0f6fef31cd324d02d1d4d21bbd8bf51032b0e3a01f4b0afd2e885f94a6976fab427b8c617c83db554f5e71
7
+ data.tar.gz: 7dd486b715dcd74ac6ae88451a39e12c61f1b2b525e62f1e52a0e18dfe0a485b3f853ff37ac6f183a30b20383c6cc7410a8cbc6fa9e0fdbcef129b9f188a0697
data/README.md CHANGED
@@ -50,22 +50,53 @@ From a security perspective it is important then to properly secure the system s
50
50
  no hacker can switch to and run as the rails user and thereby gain access to the
51
51
  encryption and decryption capabilities
52
52
 
53
+ ## Limitations
54
+
55
+ By default symmetric encryption uses the same initialization vector (IV) and
56
+ encryption key to encrypt data using the SymmetricEncryption.encrypt call.
57
+ This technique is required in cases where the encrypted data is used as a key
58
+ to lookup for example a Social Security Number, since for the same input data it
59
+ must always return the same encrypted result. The drawback is that this
60
+ technique is not considered secure when encypting large amounts of data.
61
+
62
+ For non-key fields, such as storing encrypted raw responses,
63
+ use the :random_iv => true option where possible so that a
64
+ randomly generated IV is used and included in every encrypted string.
65
+
66
+ The Symmetric Encryption streaming interface SymmetricEncryption::Writer avoids this
67
+ problem by using a random IV and key in every file/stream by default.
68
+ The random IV and key are stored in the header of the output stream so that it
69
+ is available when reading back the encrypted file/stream.
70
+
71
+ The ActiveRecord attr_encrypted method supports the :random_iv => true option.
72
+ Similarly for Mongoid the :random_iv => true option can be added.
73
+
74
+ Note that encrypting the same input string with the same key and :random_iv => true
75
+ option will result in different encrypted output every time it is encrypted.
76
+
53
77
  ## Features
54
78
 
55
79
  * Encryption of passwords in configuration files
56
80
  * Encryption of ActiveRecord model attributes by prefixing attributes / column
57
81
  names with encrypted_
82
+ * Encryption of Mongoid model fields by adding :encrypted => true to field
83
+ definitions
58
84
  * Externalization of symmetric encryption keys so that they are not in the
59
85
  source code, or the source code control system
60
- * Drop in replacement for attr_encrypted. Just remove the attr_encrypted gem
61
- * Compatible with the default Encryption algorithm in attr_encrypted
62
- * More efficient replacement for attr_encrypted since only ActiveRecord Models
63
- are extended with encrypted_ behavior, rather than every object in the system
64
- * Custom validator for ActiveRecord Models
86
+ * Validator for ActiveRecord Models to ensure fields contain encrypted data
65
87
  * Stream based encryption and decryption so that large files can be read or
66
- written with encryption
88
+ written with encryption, along with a random key and IV for every file
67
89
  * Stream based encryption and decryption also supports compression and decompression
68
90
  on the fly
91
+ * When :compress => true option is specified Symmetric Encryption will transparently
92
+ compress the data prior to decryption. When decrypting compressed data Symmetric
93
+ Encryption will transparently decompress the data after decryption based on the
94
+ header stored in the encrypted data
95
+ * Uses built-in support in Ruby for OpenSSL and Zlib for high performance and
96
+ maximum portability without introducing any additional dependencies
97
+ * Drop in replacement for attr_encrypted. Just remove the attr_encrypted gem
98
+ * For maximum security supports fully random keys and initialization vectors
99
+ extracted from the entire encryption key space
69
100
 
70
101
  ## Examples
71
102
 
@@ -215,9 +246,9 @@ Before generating keys we can use SymmetricEncryption in a standalone test envir
215
246
  ```ruby
216
247
  # Use test encryption keys
217
248
  SymmetricEncryption.cipher = SymmetricEncryption::Cipher.new(
218
- :key => '1234567890ABCDEF1234567890ABCDEF',
219
- :iv => '1234567890ABCDEF',
220
- :cipher => 'aes-128-cbc'
249
+ :key => '1234567890ABCDEF1234567890ABCDEF',
250
+ :iv => '1234567890ABCDEF',
251
+ :cipher_name => 'aes-128-cbc'
221
252
  )
222
253
  encrypted = SymmetricEncryption.encrypt('hello world')
223
254
  puts SymmetricEncryption.decrypt(encrypted)
@@ -408,7 +439,7 @@ Create a configuration file in config/symmetric-encryption.yml per the following
408
439
  development: &development_defaults
409
440
  key: 1234567890ABCDEF1234567890ABCDEF
410
441
  iv: 1234567890ABCDEF
411
- cipher: aes-128-cbc
442
+ cipher_name: aes-128-cbc
412
443
 
413
444
  test:
414
445
  <<: *development_defaults
@@ -457,7 +488,7 @@ production:
457
488
  key_filename: /etc/rails/.rails.key
458
489
  iv_filename: /etc/rails/.rails.iv
459
490
 
460
- # Encryption cipher
491
+ # Encryption cipher_name
461
492
  # Recommended values:
462
493
  # aes-256-cbc
463
494
  # 256 AES CBC Algorithm. Very strong
@@ -467,7 +498,7 @@ production:
467
498
  # 128 AES CBC Algorithm. Less strong.
468
499
  # Ruby 1.8.7 MRI Approximately 100,000 encryptions or decryptions per second
469
500
  # JRuby 1.6.7 with Ruby 1.8.7 Approximately 22,000 encryptions or decryptions per second
470
- cipher: aes-256-cbc
501
+ cipher_name: aes-256-cbc
471
502
 
472
503
  -
473
504
  # OPTIONAL:
@@ -478,27 +509,48 @@ production:
478
509
  # to be used
479
510
  key_filename: /etc/rails/.rails_old.key
480
511
  iv_filename: /etc/rails/.rails_old.iv
481
- cipher: aes-256-cbc
512
+ cipher_name: aes-256-cbc
482
513
  ```
483
514
 
484
- ## Future Enhancements
515
+ ## New features in V1.1 and V2
485
516
 
486
517
  * Ability to randomly generate a new initialization vector (iv) with every
487
- encryption and put the iv in the encrypted data as its header
518
+ encryption and put the iv in the encrypted data as its header, without having
519
+ to use SymmetricEncryption::Writer
488
520
 
489
521
  * With file encryption randomly generate a new key and initialization vector (iv) with every
490
522
  file encryption and put the key and iv in the encrypted data as its header which
491
523
  is encrypted using the global key and iv
492
524
 
493
- Submit an issue ticket to request any of the following features:
494
-
495
- * Ability to entirely disable encryption for a specific environment.
496
- SymmetricEncryption.encrypt() would return the supplied data without encrypting it and
497
- SymmetricEncryption.decrypt() would return the supplied data without decrypting it
525
+ * Support for compression via SymmetricEncryption.encrypt, attr_encrypted and Mongoid
526
+ fields
498
527
 
499
- * Support for automatically compressing data prior to encrypting it when the
500
- data exceeds some predefined size. And automatically decompressing the data
501
- during decryption
528
+ * SymmetricEncryption.encrypt has two additional optional parameters:
529
+ ```
530
+ random_iv [true|false]
531
+ Whether the encypted value should use a random IV every time the
532
+ field is encrypted.
533
+ It is recommended to set this to true where feasible. If the encrypted
534
+ value could be used as part of a SQL where clause, or as part
535
+ of any lookup, then it must be false.
536
+ Setting random_iv to true will result in a different encrypted output for
537
+ the same input string.
538
+ Note: Only set to true if the field will never be used as part of
539
+ the where clause in an SQL query.
540
+ Note: When random_iv is true it will add a 8 byte header, plus the bytes
541
+ to store the random IV in every returned encrypted string, prior to the
542
+ encoding if any.
543
+ Default: false
544
+ Highly Recommended where feasible: true
545
+
546
+ compress [true|false]
547
+ Whether to compress str before encryption
548
+ Should only be used for large strings since compression overhead and
549
+ the overhead of adding the 'magic' header may exceed any benefits of
550
+ compression
551
+ Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
552
+ Default: false
553
+ ```
502
554
 
503
555
  Meta
504
556
  ----
@@ -5,10 +5,11 @@
5
5
  # For the development and test environments the test symmetric encryption keys
6
6
  # can be placed directly in the source code.
7
7
  # And therefore no RSA private key is required
8
- development: &development_defaults
9
- key: 1234567890ABCDEF1234567890ABCDEF
10
- iv: 1234567890ABCDEF
11
- cipher: aes-128-cbc
8
+ development: &development_defaults
9
+ key: 1234567890ABCDEF1234567890ABCDEF
10
+ iv: 1234567890ABCDEF
11
+ cipher_name: aes-128-cbc
12
+ encoding: :base64strict
12
13
 
13
14
  test:
14
15
  <<: *development_defaults
@@ -27,7 +28,7 @@ release:
27
28
  # RSA public key derived from the private key above
28
29
  key_filename: <%= File.join(key_path, "#{app_name}_release.key") %>
29
30
  iv_filename: <%= File.join(key_path, "#{app_name}_release.iv") %>
30
- cipher: aes-256-cbc
31
+ cipher_name: aes-256-cbc
31
32
  # Base64 encode encrypted data without newlines
32
33
  encoding: :base64strict
33
34
  version: 1
@@ -46,7 +47,7 @@ production:
46
47
  # RSA public key derived from the private key above
47
48
  key_filename: <%= File.join(key_path, "#{app_name}_production.key") %>
48
49
  iv_filename: <%= File.join(key_path, "#{app_name}_production.iv") %>
49
- cipher: aes-256-cbc
50
+ cipher_name: aes-256-cbc
50
51
  # Base64 encode encrypted data without newlines
51
52
  encoding: :base64strict
52
53
  version: 1
@@ -7,39 +7,31 @@ 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, :version
10
+ attr_reader :cipher_name, :version
11
11
  attr_accessor :encoding
12
12
 
13
13
  # Available encodings
14
14
  ENCODINGS = [:none, :base64, :base64strict, :base16]
15
15
 
16
+ # Backward compatibility
17
+ alias_method :cipher, :cipher_name
18
+
16
19
  # Generate a new Symmetric Key pair
17
20
  #
18
21
  # Returns a hash containing a new random symmetric_key pair
19
22
  # consisting of a :key and :iv.
20
- # The cipher is also included for compatibility with the Cipher initializer
21
- def self.random_key_pair(cipher = 'aes-256-cbc', generate_iv = true)
22
- openssl_cipher = OpenSSL::Cipher.new(cipher)
23
+ # The cipher_name is also included for compatibility with the Cipher initializer
24
+ def self.random_key_pair(cipher_name = 'aes-256-cbc', generate_iv = true)
25
+ openssl_cipher = ::OpenSSL::Cipher.new(cipher_name)
23
26
  openssl_cipher.encrypt
24
27
 
25
28
  {
26
- :key => openssl_cipher.random_key,
27
- :iv => generate_iv ? openssl_cipher.random_iv : nil,
28
- :cipher => cipher
29
+ :key => openssl_cipher.random_key,
30
+ :iv => generate_iv ? openssl_cipher.random_iv : nil,
31
+ :cipher_name => cipher_name
29
32
  }
30
33
  end
31
34
 
32
- # Returns a new Cipher with a random key and iv
33
- #
34
- # The cipher and encoding used are from the global encryption cipher
35
- #
36
- def self.random_cipher(cipher=nil, encoding=nil)
37
- global_cipher = SymmetricEncryption.cipher
38
- options = random_key_pair(cipher || global_cipher.cipher)
39
- options[:encoding] = encoding || global_cipher.encoding
40
- new(options)
41
- end
42
-
43
35
  # Create a Symmetric::Key for encryption and decryption purposes
44
36
  #
45
37
  # Parameters:
@@ -50,7 +42,7 @@ module SymmetricEncryption
50
42
  # Optional. The Initialization Vector to use with Symmetric Key
51
43
  # Highly Recommended as it is the input into the CBC algorithm
52
44
  #
53
- # :cipher [String]
45
+ # :cipher_name [String]
54
46
  # Optional. Encryption Cipher to use
55
47
  # Default: aes-256-cbc
56
48
  #
@@ -76,7 +68,7 @@ module SymmetricEncryption
76
68
  def initialize(parms={})
77
69
  raise "Missing mandatory parameter :key" unless @key = parms[:key]
78
70
  @iv = parms[:iv]
79
- @cipher = parms[:cipher] || 'aes-256-cbc'
71
+ @cipher_name = parms[:cipher_name] || parms[:cipher] || 'aes-256-cbc'
80
72
  @version = parms[:version]
81
73
  raise "Cipher version has a maximum of 255. #{@version} is too high" if @version.to_i > 255
82
74
  @encoding = (parms[:encoding] || :base64).to_sym
@@ -84,28 +76,45 @@ module SymmetricEncryption
84
76
  raise("Invalid Encoding: #{@encoding}") unless ENCODINGS.include?(@encoding)
85
77
  end
86
78
 
87
- # Encryption of supplied string
88
- # The String is encoded to UTF-8 prior to encryption
79
+ # Returns encrypted and then encoded string
80
+ # Returns nil if str is nil
81
+ # Returns "" str is empty
89
82
  #
90
- # Returns result as an encoded string if encode is true
91
- # Returns nil if the supplied str is nil
92
- # Returns "" if it is a string and it is empty
93
- if defined?(Encoding)
94
- def encrypt(str, encode = true)
95
- return if str.nil?
96
- str = str.to_s #.force_encoding(SymmetricEncryption::BINARY_ENCODING)
97
- return str if str.empty?
98
- encrypted = crypt(:encrypt, str)
99
- encode ? self.encode(encrypted) : encrypted
100
- end
101
- else
102
- def encrypt(str, encode = true)
103
- return if str.nil?
104
- buf = str.to_s
105
- return str if buf.empty?
106
- encrypted = crypt(:encrypt, buf)
107
- encode ? self.encode(encrypted) : encrypted
108
- end
83
+ # Parameters
84
+ #
85
+ # str [String]
86
+ # String to be encrypted. If str is not a string, #to_s will be called on it
87
+ # to convert it to a string
88
+ #
89
+ # random_iv [true|false]
90
+ # Whether the encypted value should use a random IV every time the
91
+ # field is encrypted.
92
+ # It is recommended to set this to true where feasible. If the encrypted
93
+ # value could be used as part of a SQL where clause, or as part
94
+ # of any lookup, then it must be false.
95
+ # Setting random_iv to true will result in a different encrypted output for
96
+ # the same input string.
97
+ # Note: Only set to true if the field will never be used as part of
98
+ # the where clause in an SQL query.
99
+ # Note: When random_iv is true it will add a 8 byte header, plus the bytes
100
+ # to store the random IV in every returned encrypted string, prior to the
101
+ # encoding if any.
102
+ # Default: false
103
+ # Highly Recommended where feasible: true
104
+ #
105
+ # compress [true|false]
106
+ # Whether to compress str before encryption
107
+ # Should only be used for large strings since compression overhead and
108
+ # the overhead of adding the 'magic' header may exceed any benefits of
109
+ # compression
110
+ # Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
111
+ # Default: false
112
+ def encrypt(str, random_iv=false, compress=false)
113
+ return if str.nil?
114
+ str = str.to_s
115
+ return str if str.empty?
116
+ encrypted = binary_encrypt(str, random_iv, compress)
117
+ self.encode(encrypted)
109
118
  end
110
119
 
111
120
  # Decryption of supplied string
@@ -116,25 +125,16 @@ module SymmetricEncryption
116
125
  # Returns nil if the supplied str is nil
117
126
  # Returns "" if it is a string and it is empty
118
127
  if defined?(Encoding)
119
- def decrypt(str, decode = true)
120
- decoded = self.decode(str) if decode
121
- return unless decoded
122
-
123
- return decoded if decoded.empty?
124
- crypt(:decrypt, decoded).force_encoding(SymmetricEncryption::UTF8_ENCODING)
125
- end
126
-
127
- # Returns a binary decrypted string
128
- def decrypt_binary(str, decode = true)
129
- decoded = self.decode(str) if decode
128
+ def decrypt(str)
129
+ decoded = self.decode(str)
130
130
  return unless decoded
131
131
 
132
132
  return decoded if decoded.empty?
133
- crypt(:decrypt, decoded).force_encoding(SymmetricEncryption::BINARY_ENCODING)
133
+ binary_decrypt(decoded).force_encoding(SymmetricEncryption::UTF8_ENCODING)
134
134
  end
135
135
  else
136
- def decrypt(str, decode = true)
137
- decoded = self.decode(str) if decode
136
+ def decrypt(str)
137
+ decoded = self.decode(str)
138
138
  return unless decoded
139
139
 
140
140
  return decoded if decoded.empty?
@@ -142,15 +142,15 @@ module SymmetricEncryption
142
142
  end
143
143
  end
144
144
 
145
- # Return a new random key using the configured cipher
145
+ # Return a new random key using the configured cipher_name
146
146
  # Useful for generating new symmetric keys
147
147
  def random_key
148
- ::OpenSSL::Cipher::Cipher.new(@cipher).random_key
148
+ ::OpenSSL::Cipher::Cipher.new(@cipher_name).random_key
149
149
  end
150
150
 
151
- # Returns the block size for the configured cipher
151
+ # Returns the block size for the configured cipher_name
152
152
  def block_size
153
- ::OpenSSL::Cipher::Cipher.new(@cipher).block_size
153
+ ::OpenSSL::Cipher::Cipher.new(@cipher_name).block_size
154
154
  end
155
155
 
156
156
  # Returns UTF8 encoded string after encoding the supplied Binary string
@@ -193,12 +193,13 @@ module SymmetricEncryption
193
193
  end
194
194
  end
195
195
 
196
- # Returns an Array with the first element being Symmetric Cipher that must
197
- # be used to decrypt the data. The second element indicates whether the data
198
- # must be decompressed after decryption
199
- #
200
- # If the buffer does not start with the Magic Header the global cipher will
201
- # be returned
196
+ # Returns an Array of the following values extracted from header or nil
197
+ # if any value was not specified in the header
198
+ # compressed [true|false]
199
+ # iv [String]
200
+ # key [String]
201
+ # cipher_name [String}
202
+ # decryption_cipher [SymmetricEncryption::Cipher]
202
203
  #
203
204
  # The supplied buffer will be updated directly and will have the header
204
205
  # portion removed
@@ -215,19 +216,21 @@ module SymmetricEncryption
215
216
  # If no header is present, this is the default value for the compression
216
217
  def self.parse_magic_header!(buffer, default_version=nil, default_compressed=false)
217
218
  buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING)
218
- return [SymmetricEncryption.cipher(default_version), default_compressed] unless buffer.start_with?(MAGIC_HEADER)
219
+ return [default_compressed, nil, nil, nil, SymmetricEncryption.cipher(default_version)] unless buffer.start_with?(MAGIC_HEADER)
219
220
 
220
221
  # Header includes magic header and version byte
221
222
  # Remove header and extract flags
222
- header, flags = buffer.slice!(0..MAGIC_HEADER_SIZE+1).unpack(MAGIC_HEADER_UNPACK)
223
+ _, flags = buffer.slice!(0..MAGIC_HEADER_SIZE+1).unpack(MAGIC_HEADER_UNPACK)
223
224
  compressed = (flags & 0b1000_0000_0000_0000) != 0
224
225
  include_iv = (flags & 0b0100_0000_0000_0000) != 0
225
226
  include_key = (flags & 0b0010_0000_0000_0000) != 0
226
227
  include_cipher= (flags & 0b0001_0000_0000_0000) != 0
228
+ # Version of the key to use to decrypt the key if present,
229
+ # otherwise to decrypt the data following the header
227
230
  version = flags & 0b0000_0000_1111_1111
228
231
  decryption_cipher = SymmetricEncryption.cipher(version)
229
232
  raise "Cipher with version:#{version.inspect} not found in any of the configured SymmetricEncryption ciphers" unless decryption_cipher
230
- iv, key, cipher = nil
233
+ iv, key, cipher_name = nil
231
234
 
232
235
  if include_iv
233
236
  len = buffer.slice!(0..1).unpack('v').first
@@ -235,22 +238,14 @@ module SymmetricEncryption
235
238
  end
236
239
  if include_key
237
240
  len = buffer.slice!(0..1).unpack('v').first
238
- key = decryption_cipher.send(:crypt, :decrypt, buffer.slice!(0..len-1))
241
+ key = decryption_cipher.binary_decrypt(buffer.slice!(0..len-1))
239
242
  end
240
243
  if include_cipher
241
244
  len = buffer.slice!(0..1).unpack('v').first
242
- cipher = buffer.slice!(0..len-1)
243
- end
244
-
245
- if iv || key || cipher
246
- decryption_cipher = SymmetricEncryption::Cipher.new(
247
- :iv => iv,
248
- :key => key || decryption_cipher.key,
249
- :cipher => cipher || decryption_cipher.cipher
250
- )
245
+ cipher_name = buffer.slice!(0..len-1)
251
246
  end
252
247
 
253
- [decryption_cipher, compressed]
248
+ [compressed, iv, key, cipher_name, decryption_cipher]
254
249
  end
255
250
 
256
251
  # Returns a magic header for this cipher instance that can be placed at
@@ -259,85 +254,125 @@ module SymmetricEncryption
259
254
  # Parameters
260
255
  # compressed
261
256
  # Sets the compressed indicator in the header
257
+ # Default: false
262
258
  #
263
- # include_iv
264
- # Includes the encrypted Initialization Vector from this cipher if present
265
- # The IV is encrypted using the global encryption key
259
+ # iv
260
+ # The iv to to put in the header
261
+ # Default: nil : Exclude from header
266
262
  #
267
- # include_key
268
- # Includes the encrypted Key in this cipher
263
+ # key
264
+ # The key to to put in the header
269
265
  # The key is encrypted using the global encryption key
266
+ # Default: nil : Exclude key from header
270
267
  #
271
- # include_cipher
272
- # Includes the cipher used. For example 'aes-256-cbc'
273
- #
274
- # encryption_cipher
275
- # Encryption cipher to use when encrypting the iv and key.
276
- # When supplied, the version is set to it's version so that decryption
277
- # knows which cipher to use
278
- # Default: Global cipher: SymmetricEncryption.cipher
279
- def magic_header(compressed=false, include_iv=false, include_key=false, include_cipher=false, encryption_cipher=nil)
268
+ # cipher_name
269
+ # Includes the cipher_name used. For example 'aes-256-cbc'
270
+ # The cipher_name string to to put in the header
271
+ # Default: nil : Exclude cipher_name name from header
272
+ def self.magic_header(version, compressed=false, iv=nil, key=nil, cipher_name=nil)
280
273
  # Ruby V2 named parameters would be perfect here
281
274
 
282
275
  # Encryption version indicator if available
283
276
  flags = version || 0 # Same as 0b0000_0000_0000_0000
284
277
 
285
- # Replace version with cipher used to encrypt Random IV and Key
286
- if include_iv || include_key
287
- encryption_cipher ||= SymmetricEncryption.cipher
288
- flags = (encryption_cipher.version || 0)
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)
289
281
  end
290
282
 
291
283
  # If the data is to be compressed before being encrypted, set the
292
284
  # compressed bit in the flags word
293
285
  flags |= 0b1000_0000_0000_0000 if compressed
294
- flags |= 0b0100_0000_0000_0000 if @iv && include_iv
295
- flags |= 0b0010_0000_0000_0000 if include_key
296
- flags |= 0b0001_0000_0000_0000 if include_cipher
286
+ flags |= 0b0100_0000_0000_0000 if iv
287
+ flags |= 0b0010_0000_0000_0000 if key
288
+ flags |= 0b0001_0000_0000_0000 if cipher_name
297
289
  header = "#{MAGIC_HEADER}#{[flags].pack('v')}".force_encoding(SymmetricEncryption::BINARY_ENCODING)
298
- if @iv && include_iv
299
- header << [@iv.length].pack('v')
300
- header << @iv
290
+ if iv
291
+ header << [iv.length].pack('v')
292
+ header << iv
301
293
  end
302
- if include_key
303
- encrypted = encryption_cipher.crypt(:encrypt, @key).force_encoding(SymmetricEncryption::BINARY_ENCODING)
294
+ if key
295
+ encrypted = SymmetricEncryption.cipher.binary_encrypt(key, false, false)
304
296
  header << [encrypted.length].pack('v').force_encoding(SymmetricEncryption::BINARY_ENCODING)
305
297
  header << encrypted
306
298
  end
307
- if include_cipher
308
- header << [cipher.length].pack('v')
309
- header << cipher
299
+ if cipher_name
300
+ header << [cipher_name.length].pack('v')
301
+ header << cipher_name
310
302
  end
311
303
  header
312
304
  end
313
305
 
314
- protected
315
-
316
- # Only for use by Symmetric::EncryptedStream
317
- def openssl_cipher(cipher_method)
318
- openssl_cipher = ::OpenSSL::Cipher.new(self.cipher)
319
- openssl_cipher.send(cipher_method)
306
+ # Advanced use only
307
+ #
308
+ # Returns a Binary encrypted string without applying any Base64, or other encoding
309
+ #
310
+ # Adds the 'magic' header if a random_iv is required or compression is enabled
311
+ #
312
+ # Creates a new OpenSSL::Cipher with every call so that this call
313
+ # is thread-safe
314
+ #
315
+ # See #encrypt to encrypt and encode the result as a string
316
+ def binary_encrypt(string, random_iv=false, compress=false)
317
+ openssl_cipher = ::OpenSSL::Cipher.new(self.cipher_name)
318
+ openssl_cipher.encrypt
320
319
  openssl_cipher.key = @key
321
- openssl_cipher.iv = @iv if @iv
322
- openssl_cipher
320
+ result = if random_iv || compress
321
+ # Random iv and compress both add the magic header
322
+ iv = random_iv ? openssl_cipher.random_iv : @iv
323
+ openssl_cipher.iv = iv if iv
324
+ self.class.magic_header(version, compress, random_iv ? iv : nil) +
325
+ openssl_cipher.update(compress ? Zlib::Deflate.deflate(string) : string)
326
+ else
327
+ openssl_cipher.iv = @iv if @iv
328
+ openssl_cipher.update(string)
329
+ end
330
+ result << openssl_cipher.final
323
331
  end
324
332
 
333
+ # Advanced use only
334
+ #
335
+ # Returns a Binary decrypted string without decoding the string first
336
+ #
337
+ # Reads the 'magic' header if present for key, iv, cipher_name and compression
338
+ #
339
+ # encrypted_string must be in raw binary form when calling this method
340
+ #
325
341
  # Creates a new OpenSSL::Cipher with every call so that this call
326
342
  # is thread-safe
327
- # Return a binary encoded decrypted or encrypted string
328
- def crypt(cipher_method, string) #:nodoc:
329
- openssl_cipher = ::OpenSSL::Cipher.new(self.cipher)
330
- openssl_cipher.send(cipher_method)
331
- openssl_cipher.key = @key
332
- openssl_cipher.iv = @iv if @iv
333
- result = openssl_cipher.update(string)
334
- result << openssl_cipher.final
335
- result.force_encoding(SymmetricEncryption::BINARY_ENCODING)
343
+ #
344
+ # See #decrypt to decrypt encoded strings
345
+ def binary_decrypt(encrypted_string)
346
+ str = encrypted_string.to_s
347
+ if str.start_with?(MAGIC_HEADER)
348
+ str = str.dup
349
+ compressed, iv, key, cipher_name = self.class.parse_magic_header!(str)
350
+ openssl_cipher = ::OpenSSL::Cipher.new(cipher_name || self.cipher_name)
351
+ openssl_cipher.decrypt
352
+ openssl_cipher.key = key || @key
353
+ iv ||= @iv
354
+ openssl_cipher.iv = iv if iv
355
+ result = openssl_cipher.update(str)
356
+ result << openssl_cipher.final
357
+ compressed ? Zlib::Inflate.inflate(result) : result
358
+ else
359
+ openssl_cipher = ::OpenSSL::Cipher.new(self.cipher_name)
360
+ openssl_cipher.decrypt
361
+ openssl_cipher.key = @key
362
+ openssl_cipher.iv = @iv if @iv
363
+ result = openssl_cipher.update(encrypted_string)
364
+ result << openssl_cipher.final
365
+ end
366
+ end
367
+
368
+ # Returns [String] object represented as a string
369
+ # Excluding the key and iv
370
+ def inspect
371
+ "#<#{self.class}:0x#{self.__id__.to_s(16)} @cipher_name=#{cipher_name.inspect}, @version=#{version.inspect}, @encoding=#{encoding.inspect}"
336
372
  end
337
373
 
338
374
  private
339
375
 
340
376
  attr_reader :key, :iv
341
-
342
377
  end
343
378
  end