symmetric-encryption 3.0.3 → 3.1.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: e9372dc10ff0f61fac4ce5b15b1166c12e0623e1
4
- data.tar.gz: b5953a44d66dff085cd6417622bad5e0ca7cdeb5
3
+ metadata.gz: 5b55176807bf8e48cedee2a8f6d40a12c7119200
4
+ data.tar.gz: f8e5b0547683314ff7c1212989ba81a4cad1439c
5
5
  SHA512:
6
- metadata.gz: 1bb09ce303a73a7031f4c8b6f8561ed3fd60239cdd75d7ad8251bdcb4708273d21207f2ae007fb1898ecc00e6b8fefbf6a7b318577105eee009da2825e2aee43
7
- data.tar.gz: 1e579b442711e49ac774efcd53cf453a862e6917e1469053fe52fc310cf114059ee5c121a60d0e3243016fcb8b2234fffa4f63e987c4e4cc7646fc8dbf88e230
6
+ metadata.gz: dae35d6143650d7a3ea7b3d181a70456088d9a26341bb2c7d83b4aab6f7969b271965c24d0dda7fe906cb26702a7dbdd964c93cecf1df9310dd6d0239acad2e8
7
+ data.tar.gz: 19e4120aa1a617601c41002326256743ef8ea707001e14f7d3f324e2e8675ec1067bc45c4561f43e9daf7a25ef005e408d5aff4dee0daa1100aab513495ad2f2
data/README.md CHANGED
@@ -96,7 +96,8 @@ randomly generated IV is used and included in every encrypted string.
96
96
  The Symmetric Encryption streaming interface SymmetricEncryption::Writer avoids this
97
97
  problem by using a random IV and key in every file/stream by default.
98
98
  The random IV and key are stored in the header of the output stream so that it
99
- is available when reading back the encrypted file/stream.
99
+ is available when reading back the encrypted file/stream. The key is placed
100
+ in a header on the file in encrypted form using the current global key/cipher.
100
101
 
101
102
  The ActiveRecord attr_encrypted method supports the :random_iv => true option.
102
103
  Similarly for Mongoid the :random_iv => true option can be added.
@@ -136,6 +137,11 @@ option will result in different encrypted output every time it is encrypted.
136
137
  * Set :random_iv => true for all ActiveRecord attributes and Mongoid fields
137
138
  which are not used in indexes and will not be used as part of a query.
138
139
 
140
+ ## Binary Data
141
+
142
+ On decryption an attempt is made to encode the data as UTF-8, if it fails it
143
+ will be returned as BINARY encoded.
144
+
139
145
  ## Examples
140
146
 
141
147
  ### Encryption Example
@@ -340,6 +346,11 @@ The file header also contains a random key and iv used to encrypt the files cont
340
346
  The key and iv is encrypted with the global encryption key being used by the symmetric
341
347
  encryption installation.
342
348
 
349
+ ## Dependencies
350
+
351
+ - Ruby 1.9.3 (or above) Or, JRuby 1.7.3 (or above)
352
+ - Optional: To log to MongoDB, Mongo Ruby Driver 1.5.2 or above
353
+
343
354
  ## Installation
344
355
 
345
356
  ### Add to an existing Rails project
@@ -19,7 +19,6 @@ module SymmetricEncryption
19
19
  # Defines the Header Structure returned when parsing the header
20
20
  HeaderStruct = Struct.new(
21
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
22
  :iv, # [String] IV used to encrypt the data, if supplied in the header
24
23
  :key, # [String] Key used to encrypt the data, if supplied in the header
25
24
  :cipher_name, # [String] Name of the cipher used, if supplied in the header
@@ -100,7 +99,7 @@ module SymmetricEncryption
100
99
  parms.each_pair {|k,v| warn "SymmetricEncryption::Cipher Ignoring unknown option #{k.inspect} = #{v.inspect}"}
101
100
  end
102
101
 
103
- # Encrypt and then encode a binary or UTF-8 string
102
+ # Encrypt and then encode a string
104
103
  #
105
104
  # Returns data encrypted and then encoded according to the encoding setting
106
105
  # of this cipher
@@ -154,14 +153,7 @@ module SymmetricEncryption
154
153
  # encrypted_string [String]
155
154
  # Binary encrypted string to decrypt
156
155
  #
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
156
+ # Reads the header if present for key, iv, cipher_name and compression
165
157
  #
166
158
  # encrypted_string must be in raw binary form when calling this method
167
159
  #
@@ -173,7 +165,14 @@ module SymmetricEncryption
173
165
  return unless decoded
174
166
 
175
167
  return decoded if decoded.empty?
176
- binary_decrypt(decoded).force_encoding(SymmetricEncryption::UTF8_ENCODING)
168
+ decrypted = binary_decrypt(decoded)
169
+ if defined?(Encoding)
170
+ # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
171
+ unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
172
+ decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
173
+ end
174
+ end
175
+ decrypted
177
176
  end
178
177
 
179
178
  # Returns UTF8 encoded string after encoding the supplied Binary string
@@ -283,7 +282,6 @@ module SymmetricEncryption
283
282
  include_iv = (flags & 0b0100_0000_0000_0000) != 0
284
283
  include_key = (flags & 0b0010_0000_0000_0000) != 0
285
284
  include_cipher= (flags & 0b0001_0000_0000_0000) != 0
286
- binary = (flags & 0b0000_1000_0000_0000) != 0
287
285
  # Version of the key to use to decrypt the key if present,
288
286
  # otherwise to decrypt the data following the header
289
287
  version = flags & 0b0000_0000_1111_1111
@@ -297,14 +295,14 @@ module SymmetricEncryption
297
295
  end
298
296
  if include_key
299
297
  len = buffer.slice!(0..1).unpack('v').first
300
- key = decryption_cipher.binary_decrypt(buffer.slice!(0..len-1), header=false, binary=true)
298
+ key = decryption_cipher.binary_decrypt(buffer.slice!(0..len-1), header=false)
301
299
  end
302
300
  if include_cipher
303
301
  len = buffer.slice!(0..1).unpack('v').first
304
302
  cipher_name = buffer.slice!(0..len-1)
305
303
  end
306
304
 
307
- HeaderStruct.new(compressed, binary, iv, key, cipher_name, version, decryption_cipher)
305
+ HeaderStruct.new(compressed, iv, key, cipher_name, version, decryption_cipher)
308
306
  end
309
307
 
310
308
  # Returns a magic header for this cipher instance that can be placed at
@@ -328,11 +326,7 @@ module SymmetricEncryption
328
326
  # Includes the cipher_name used. For example 'aes-256-cbc'
329
327
  # The cipher_name string to to put in the header
330
328
  # Default: nil : Exclude cipher_name name from header
331
- #
332
- # binary
333
- # Whether the data being encrypted is binary.
334
- # When the header is read, it sets the encoding of the string returned to Binary
335
- def self.build_header(version, compressed=false, iv=nil, key=nil, cipher_name=nil, binary=false)
329
+ def self.build_header(version, compressed=false, iv=nil, key=nil, cipher_name=nil)
336
330
  # Ruby V2 named parameters would be perfect here
337
331
 
338
332
  # Version number of supplied encryption key, or use the global cipher version if none was supplied
@@ -344,7 +338,6 @@ module SymmetricEncryption
344
338
  flags |= 0b0100_0000_0000_0000 if iv
345
339
  flags |= 0b0010_0000_0000_0000 if key
346
340
  flags |= 0b0001_0000_0000_0000 if cipher_name
347
- flags |= 0b0000_1000_0000_0000 if binary
348
341
  header = "#{MAGIC_HEADER}#{[flags].pack('v')}".force_encoding(SymmetricEncryption::BINARY_ENCODING)
349
342
  if iv
350
343
  header << [iv.length].pack('v')
@@ -391,8 +384,7 @@ module SymmetricEncryption
391
384
  iv = random_iv ? openssl_cipher.random_iv : @iv
392
385
  openssl_cipher.iv = iv if iv
393
386
  # Set the binary indicator on the header if string is Binary Encoded
394
- binary = (string.encoding == SymmetricEncryption::BINARY_ENCODING)
395
- self.class.build_header(version, compress, random_iv ? iv : nil, nil, nil, binary) +
387
+ self.class.build_header(version, compress, random_iv ? iv : nil, nil, nil) +
396
388
  openssl_cipher.update(compress ? Zlib::Deflate.deflate(string) : string)
397
389
  else
398
390
  openssl_cipher.iv = @iv if @iv
@@ -405,6 +397,7 @@ module SymmetricEncryption
405
397
  # See #decrypt to decrypt encoded strings
406
398
  #
407
399
  # Returns a Binary decrypted string without decoding the string first
400
+ # The returned string has BINARY encoding
408
401
  #
409
402
  # Decryption of supplied string
410
403
  # Returns the decrypted string
@@ -418,10 +411,6 @@ module SymmetricEncryption
418
411
  # header [HeaderStruct]
419
412
  # Optional header for the supplied encrypted_string
420
413
  #
421
- # binary [true|false]
422
- # If no header is supplied then determines whether the string returned
423
- # is binary or UTF8
424
- #
425
414
  # Reads the 'magic' header if present for key, iv, cipher_name and compression
426
415
  #
427
416
  # encrypted_string must be in raw binary form when calling this method
@@ -433,16 +422,15 @@ module SymmetricEncryption
433
422
  # Note:
434
423
  # When a string is encrypted and the header is used, its decrypted form
435
424
  # is automatically set to the same UTF-8 or Binary encoding
436
- def binary_decrypt(encrypted_string, header=nil, binary=false)
425
+ def binary_decrypt(encrypted_string, header=nil)
437
426
  return if encrypted_string.nil?
438
427
  str = encrypted_string.to_s
439
428
  str.force_encoding(SymmetricEncryption::BINARY_ENCODING) if str.respond_to?(:force_encoding)
440
429
  return str if str.empty?
441
430
 
442
- decrypted_string = if header || self.class.has_header?(str)
431
+ if header || self.class.has_header?(str)
443
432
  str = str.dup
444
433
  header ||= self.class.parse_header!(str)
445
- binary = header.binary
446
434
 
447
435
  openssl_cipher = ::OpenSSL::Cipher.new(header.cipher_name || self.cipher_name)
448
436
  openssl_cipher.decrypt
@@ -460,19 +448,11 @@ module SymmetricEncryption
460
448
  result = openssl_cipher.update(str)
461
449
  result << openssl_cipher.final
462
450
  end
463
-
464
- # Support Ruby 1.9 and above Encoding
465
- if defined?(Encoding)
466
- # Sets the encoding of the result string to UTF8 or BINARY based on the binary header
467
- binary ? decrypted_string.force_encoding(SymmetricEncryption::BINARY_ENCODING) : decrypted_string.force_encoding(SymmetricEncryption::UTF8_ENCODING)
468
- else
469
- decrypted_string
470
- end
471
451
  end
472
452
 
473
453
  # Returns [String] object represented as a string, filtering out the key
474
454
  def inspect
475
- "#<#{self.class}:0x#{self.__id__.to_s(16)} @key=\"[FILTERED]\" @iv=#{iv.inspect} @cipher_name=#{cipher_name.inspect}, @version=#{version.inspect}, @encoding=#{encoding.inspect}"
455
+ "#<#{self.class}:0x#{self.__id__.to_s(16)} @key=\"[FILTERED]\" @iv=#{iv.inspect} @cipher_name=#{cipher_name.inspect}, @version=#{version.inspect}, @encoding=#{encoding.inspect}, @always_add_header=#{always_add_header.inspect}"
476
456
  end
477
457
 
478
458
  private
@@ -90,13 +90,21 @@ module SymmetricEncryption
90
90
  return unless decoded
91
91
  return decoded if decoded.empty?
92
92
 
93
- if header = Cipher.parse_header!(decoded)
93
+ decrypted = if header = Cipher.parse_header!(decoded)
94
94
  header.decryption_cipher.binary_decrypt(decoded, header)
95
95
  else
96
96
  # Use cipher_selector if present to decide which cipher to use
97
97
  c = @@select_cipher.nil? ? cipher(version) : @@select_cipher.call(str, decoded)
98
98
  c.binary_decrypt(decoded)
99
99
  end
100
+
101
+ if defined?(Encoding)
102
+ # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
103
+ unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
104
+ decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
105
+ end
106
+ end
107
+ decrypted
100
108
  end
101
109
 
102
110
  # AES Symmetric Encryption of supplied string
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module SymmetricEncryption #:nodoc
3
- VERSION = "3.0.3"
2
+ VERSION = "3.1.0"
4
3
  end
data/test/cipher_test.rb CHANGED
@@ -52,12 +52,81 @@ class CipherTest < Test::Unit::TestCase
52
52
 
53
53
  end
54
54
 
55
+ [false, true].each do |always_add_header|
56
+ SymmetricEncryption::Cipher::ENCODINGS.each do |encoding|
57
+ context "encoding: #{encoding} with#{'out' unless always_add_header} header" do
58
+ setup do
59
+ @social_security_number = "987654321"
60
+ @social_security_number_encrypted =
61
+ case encoding
62
+ when :base64
63
+ always_add_header ? "QEVuQwAAyTeLjsHTa8ykoO95K0KQmg==\n" : "yTeLjsHTa8ykoO95K0KQmg==\n"
64
+ when :base64strict
65
+ always_add_header ? "QEVuQwAAyTeLjsHTa8ykoO95K0KQmg==" : "yTeLjsHTa8ykoO95K0KQmg=="
66
+ when :base16
67
+ always_add_header ? "40456e430000c9378b8ec1d36bcca4a0ef792b42909a" : "c9378b8ec1d36bcca4a0ef792b42909a"
68
+ when :none
69
+ bin = always_add_header ? "@EnC\x00\x00\xC97\x8B\x8E\xC1\xD3k\xCC\xA4\xA0\xEFy+B\x90\x9A" : "\xC97\x8B\x8E\xC1\xD3k\xCC\xA4\xA0\xEFy+B\x90\x9A"
70
+ bin.force_encoding(Encoding.find("binary"))
71
+ else
72
+ raise "Add test for encoding: #{encoding}"
73
+ end
74
+ @social_security_number_encrypted_with_secondary_1 = "D1UCu38pqJ3jc0GvwJHiow==\n"
75
+ @non_utf8 = "\xc2".force_encoding('binary')
76
+ @cipher = SymmetricEncryption::Cipher.new(
77
+ key: 'ABCDEF1234567890ABCDEF1234567890',
78
+ iv: 'ABCDEF1234567890',
79
+ cipher_name: 'aes-128-cbc',
80
+ encoding: encoding,
81
+ always_add_header: always_add_header
82
+ )
83
+ end
84
+
85
+ should "encrypt simple string" do
86
+ assert_equal @social_security_number_encrypted, @cipher.encrypt(@social_security_number)
87
+ end
88
+
89
+ should "decrypt string" do
90
+ assert decrypted = @cipher.decrypt(@social_security_number_encrypted)
91
+ assert_equal @social_security_number, decrypted
92
+ assert_equal Encoding.find('utf-8'), decrypted.encoding, decrypted
93
+ end
94
+
95
+ should 'return BINARY encoding for non-UTF-8 encrypted data' do
96
+ assert_equal Encoding.find('binary'), @non_utf8.encoding
97
+ assert_equal true, @non_utf8.valid_encoding?
98
+ assert encrypted = @cipher.encrypt(@non_utf8)
99
+ assert decrypted = @cipher.decrypt(encrypted)
100
+ assert_equal true, decrypted.valid_encoding?
101
+ assert_equal Encoding.find('binary'), decrypted.encoding, decrypted
102
+ assert_equal @non_utf8, decrypted
103
+ end
104
+
105
+ should "return nil when encrypting nil" do
106
+ assert_equal nil, @cipher.encrypt(nil)
107
+ end
108
+
109
+ should "return '' when encrypting ''" do
110
+ assert_equal '', @cipher.encrypt('')
111
+ end
112
+
113
+ should "return nil when decrypting nil" do
114
+ assert_equal nil, @cipher.decrypt(nil)
115
+ end
116
+
117
+ should "return '' when decrypting ''" do
118
+ assert_equal '', @cipher.decrypt('')
119
+ end
120
+ end
121
+ end
122
+ end
123
+
55
124
  context 'with configuration' do
56
125
  setup do
57
126
  @cipher = SymmetricEncryption::Cipher.new(
58
- :key => '1234567890ABCDEF1234567890ABCDEF',
59
- :iv => '1234567890ABCDEF',
60
- :encoding => :none
127
+ :key => '1234567890ABCDEF1234567890ABCDEF',
128
+ :iv => '1234567890ABCDEF',
129
+ :encoding => :none
61
130
  )
62
131
  @social_security_number = "987654321"
63
132
 
@@ -73,42 +142,6 @@ class CipherTest < Test::Unit::TestCase
73
142
  assert_equal 'aes-256-cbc', @cipher.cipher_name
74
143
  end
75
144
 
76
- should "encrypt simple string" do
77
- assert_equal @social_security_number_encrypted, @cipher.encrypt(@social_security_number)
78
- end
79
-
80
- should "return nil when encrypting nil" do
81
- assert_equal nil, @cipher.encrypt(nil)
82
- end
83
-
84
- should "return '' when encrypting ''" do
85
- assert_equal '', @cipher.encrypt('')
86
- end
87
-
88
- should "return nil when decrypting nil" do
89
- assert_equal nil, @cipher.decrypt(nil)
90
- end
91
-
92
- should "return '' when decrypting ''" do
93
- assert_equal '', @cipher.decrypt('')
94
- end
95
-
96
- should "decrypt string" do
97
- assert_equal @social_security_number, @cipher.decrypt(@social_security_number_encrypted)
98
- end
99
-
100
- if defined?(Encoding)
101
- context "on Ruby 1.9" do
102
- should "encode encrypted data as binary" do
103
- assert_equal Encoding.find('binary'), @cipher.encrypt(@social_security_number).encoding
104
- end
105
-
106
- should "decode encrypted data as utf-8" do
107
- assert_equal Encoding.find('utf-8'), @cipher.decrypt(@cipher.encrypt(@social_security_number)).encoding
108
- end
109
- end
110
- end
111
-
112
145
  context "with header" do
113
146
  setup do
114
147
  @social_security_number = "987654321"
@@ -116,7 +149,7 @@ class CipherTest < Test::Unit::TestCase
116
149
 
117
150
  should "build and parse header" do
118
151
  assert random_key_pair = SymmetricEncryption::Cipher.random_key_pair('aes-128-cbc')
119
- assert binary_header = SymmetricEncryption::Cipher.build_header(SymmetricEncryption.cipher.version, compressed=true, random_key_pair[:iv], random_key_pair[:key], random_key_pair[:cipher_name], binary=true)
152
+ assert binary_header = SymmetricEncryption::Cipher.build_header(SymmetricEncryption.cipher.version, compressed=true, random_key_pair[:iv], random_key_pair[:key], random_key_pair[:cipher_name])
120
153
  header = SymmetricEncryption::Cipher.parse_header!(binary_header)
121
154
  assert_equal true, header.compressed
122
155
  assert random_cipher = SymmetricEncryption::Cipher.new(random_key_pair)
data/test/reader_test.rb CHANGED
@@ -31,8 +31,7 @@ class ReaderTest < Test::Unit::TestCase
31
31
  compress = false,
32
32
  @cipher.send(:iv),
33
33
  @cipher.send(:key),
34
- @cipher.cipher_name,
35
- binary=false,
34
+ @cipher.cipher_name
36
35
  )
37
36
  @data_encrypted_with_header << @cipher.binary_encrypt(@data_str)
38
37
 
@@ -67,6 +67,7 @@ class SymmetricEncryptionTest < Test::Unit::TestCase
67
67
  raise "Add test for encoding: #{encoding}"
68
68
  end
69
69
  @social_security_number_encrypted_with_secondary_1 = "D1UCu38pqJ3jc0GvwJHiow==\n"
70
+ @non_utf8 = "\xc2".force_encoding('binary')
70
71
  @encoding = SymmetricEncryption.cipher.encoding
71
72
  SymmetricEncryption.cipher.encoding = encoding
72
73
  end
@@ -80,7 +81,19 @@ class SymmetricEncryptionTest < Test::Unit::TestCase
80
81
  end
81
82
 
82
83
  should "decrypt string" do
83
- assert_equal @social_security_number, SymmetricEncryption.decrypt(@social_security_number_encrypted)
84
+ assert decrypted = SymmetricEncryption.decrypt(@social_security_number_encrypted)
85
+ assert_equal @social_security_number, decrypted
86
+ assert_equal Encoding.find('utf-8'), decrypted.encoding, decrypted
87
+ end
88
+
89
+ should 'return BINARY encoding for non-UTF-8 encrypted data' do
90
+ assert_equal Encoding.find('binary'), @non_utf8.encoding
91
+ assert_equal true, @non_utf8.valid_encoding?
92
+ assert encrypted = SymmetricEncryption.encrypt(@non_utf8)
93
+ assert decrypted = SymmetricEncryption.decrypt(encrypted)
94
+ assert_equal true, decrypted.valid_encoding?
95
+ assert_equal Encoding.find('binary'), decrypted.encoding, decrypted
96
+ assert_equal @non_utf8, decrypted
84
97
  end
85
98
 
86
99
  should "return nil when encrypting nil" do
data/test/test_db.sqlite3 CHANGED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: symmetric-encryption
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.3
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-20 00:00:00.000000000 Z
11
+ date: 2013-09-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: SymmetricEncryption supports encrypting ActiveRecord data, Mongoid data,
14
14
  passwords in configuration files, encrypting and decrypting of large files through