diff --git a/lib/zip/crypto/null_encryption.rb b/lib/zip/crypto/null_encryption.rb index 37e03ede..38dafa8f 100644 --- a/lib/zip/crypto/null_encryption.rb +++ b/lib/zip/crypto/null_encryption.rb @@ -12,7 +12,7 @@ def gp_flags class NullEncrypter < Encrypter include NullEncryption - def header(crc32) + def header(mtime) '' end @@ -20,6 +20,10 @@ def encrypt(data) data end + def data_descriptor(crc32, compressed_size, uncomprssed_size) + '' + end + def reset! end end diff --git a/lib/zip/crypto/traditional_encryption.rb b/lib/zip/crypto/traditional_encryption.rb index 163bf5b6..b57541a9 100644 --- a/lib/zip/crypto/traditional_encryption.rb +++ b/lib/zip/crypto/traditional_encryption.rb @@ -10,7 +10,7 @@ def header_bytesize end def gp_flags - 1 + 0x0001 | 0x0008 end protected @@ -39,12 +39,13 @@ def decrypt_byte class TraditionalEncrypter < Encrypter include TraditionalEncryption - def header(crc32) + def header(mtime) [].tap do |header| - (header_bytesize - 1).times do + (header_bytesize - 2).times do header << Random.rand(0..255) end - header << (crc32 >> 24) + header << (mtime.to_binary_dos_time & 0xff) + header << (mtime.to_binary_dos_time >> 8) end.map{|x| encode x}.pack("C*") end @@ -52,6 +53,10 @@ def encrypt(data) data.unpack("C*").map{|x| encode x}.pack("C*") end + def data_descriptor(crc32, compressed_size, uncomprssed_size) + [0x08074b50, crc32, compressed_size, uncomprssed_size].pack("VVVV") + end + def reset! reset_keys! end diff --git a/lib/zip/deflater.rb b/lib/zip/deflater.rb index d4340589..ffcebb64 100755 --- a/lib/zip/deflater.rb +++ b/lib/zip/deflater.rb @@ -5,7 +5,7 @@ def initialize(output_stream, level = Zip.default_compression, encrypter = NullE super() @output_stream = output_stream @zlib_deflater = ::Zlib::Deflate.new(level, -::Zlib::MAX_WBITS) - @size = encrypter.header_bytesize + @size = 0 @crc = ::Zlib.crc32 @encrypter = encrypter @buffer_stream = ::StringIO.new('') @@ -19,7 +19,6 @@ def << (data) end def finish - @output_stream << @encrypter.header(@crc) @output_stream << @encrypter.encrypt(@buffer_stream.string) @output_stream << @encrypter.encrypt(@zlib_deflater.finish) until @zlib_deflater.finished? end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 281bd8fa..05ca23b4 100755 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -143,7 +143,7 @@ def cdir_header_size #:nodoc:all end def next_header_offset #:nodoc:all - local_entry_offset + self.compressed_size + local_entry_offset + self.compressed_size + data_descriptor_size end # Extracts entry to file dest_path (defaults to @name). @@ -648,6 +648,10 @@ def parse_zip64_extra(for_local_header) #:nodoc:all end end + def data_descriptor_size + (@gp_flags & 0x0008) > 0 ? 16 : 0 + end + # create a zip64 extra information field if we need one def prep_zip64_extra(for_local_header) #:nodoc:all return unless ::Zip.write_zip64_support diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index f28e5759..e9c54731 100755 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -123,10 +123,12 @@ def copy_raw_entry(entry) def finalize_current_entry return unless @current_entry + @output_stream << @encrypter.header(@current_entry.mtime) finish @current_entry.compressed_size = @output_stream.tell - @current_entry.local_header_offset - @current_entry.calculate_local_header_size @current_entry.size = @compressor.size @current_entry.crc = @compressor.crc + @output_stream << @encrypter.data_descriptor(@current_entry.crc, @current_entry.compressed_size, @current_entry.size) @current_entry.gp_flags |= @encrypter.gp_flags @current_entry = nil @compressor = ::Zip::NullCompressor.instance diff --git a/test/crypto/null_encryption_test.rb b/test/crypto/null_encryption_test.rb index fa54b025..b9452a03 100644 --- a/test/crypto/null_encryption_test.rb +++ b/test/crypto/null_encryption_test.rb @@ -14,9 +14,7 @@ def test_gp_flags end def test_header - [nil, '', 'a' * 10, 0xffffffff].each do |arg| - assert_empty @encrypter.header(arg) - end + assert_empty @encrypter.header(nil) end def test_encrypt diff --git a/test/crypto/traditional_encryption_test.rb b/test/crypto/traditional_encryption_test.rb index fb1e4ff1..3e743946 100644 --- a/test/crypto/traditional_encryption_test.rb +++ b/test/crypto/traditional_encryption_test.rb @@ -2,6 +2,7 @@ class TraditionalEncrypterTest < MiniTest::Test def setup + @mtime = ::Zip::DOSTime.new(2014, 12, 17, 15, 56, 24) @encrypter = ::Zip::TraditionalEncrypter.new('password') end @@ -10,36 +11,36 @@ def test_header_bytesize end def test_gp_flags - assert_equal 1, @encrypter.gp_flags + assert_equal 9, @encrypter.gp_flags end def test_header @encrypter.reset! - exepected = [239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 116, 154].pack("C*") + exepected = [239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 121, 91].pack("C*") Random.stub(:rand, 1) do - assert_equal exepected, @encrypter.header(0xffffffff) + assert_equal exepected, @encrypter.header(@mtime) end end def test_encrypt @encrypter.reset! - Random.stub(:rand, 1) { @encrypter.header(0xffffffff) } + Random.stub(:rand, 1) { @encrypter.header(@mtime) } assert_raises(NoMethodError) { @encrypter.encrypt(nil) } assert_raises(NoMethodError) { @encrypter.encrypt(1) } assert_equal '', @encrypter.encrypt('') - assert_equal [2, 25, 13, 222, 17, 190, 250, 133, 133, 166].pack("C*"), @encrypter.encrypt('a' * 10) + assert_equal [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].pack("C*"), @encrypter.encrypt('a' * 10) end def test_reset! @encrypter.reset! - Random.stub(:rand, 1) { @encrypter.header(0xffffffff) } - [2, 25, 13, 222, 17, 190, 250, 133, 133, 166].map(&:chr).each do |c| + Random.stub(:rand, 1) { @encrypter.header(@mtime) } + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| assert_equal c, @encrypter.encrypt('a') end - assert_equal 134.chr, @encrypter.encrypt('a') + assert_equal 56.chr, @encrypter.encrypt('a') @encrypter.reset! - Random.stub(:rand, 1) { @encrypter.header(0xffffffff) } - [2, 25, 13, 222, 17, 190, 250, 133, 133, 166].map(&:chr).each do |c| + Random.stub(:rand, 1) { @encrypter.header(@mtime) } + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| assert_equal c, @encrypter.encrypt('a') end end @@ -55,24 +56,24 @@ def test_header_bytesize end def test_gp_flags - assert_equal 1, @decrypter.gp_flags + assert_equal 9, @decrypter.gp_flags end def test_decrypt - @decrypter.reset!([239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 116, 154].pack("C*")) - [2, 25, 13, 222, 17, 190, 250, 133, 133, 166].map(&:chr).each do |c| + @decrypter.reset!([239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 121, 91].pack("C*")) + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| assert_equal 'a', @decrypter.decrypt(c) end end def test_reset! - @decrypter.reset!([239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 116, 154].pack("C*")) - [2, 25, 13, 222, 17, 190, 250, 133, 133, 166].map(&:chr).each do |c| + @decrypter.reset!([239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 121, 91].pack("C*")) + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| assert_equal 'a', @decrypter.decrypt(c) end - assert_equal 229.chr, @decrypter.decrypt(2.chr) - @decrypter.reset!([239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 116, 154].pack("C*")) - [2, 25, 13, 222, 17, 190, 250, 133, 133, 166].map(&:chr).each do |c| + assert_equal 91.chr, @decrypter.decrypt(2.chr) + @decrypter.reset!([239, 57, 234, 154, 246, 80, 83, 221, 74, 200, 121, 91].pack("C*")) + [100, 218, 7, 114, 226, 82, 62, 93, 224, 62].map(&:chr).each do |c| assert_equal 'a', @decrypter.decrypt(c) end end diff --git a/test/data/zipWithEncryption.zip b/test/data/zipWithEncryption.zip new file mode 100644 index 00000000..e102b875 Binary files /dev/null and b/test/data/zipWithEncryption.zip differ diff --git a/test/encryption_test.rb b/test/encryption_test.rb new file mode 100644 index 00000000..32b367cd --- /dev/null +++ b/test/encryption_test.rb @@ -0,0 +1,33 @@ +require 'test_helper' + +class EncryptionTest < MiniTest::Test + ENCRYPT_ZIP_TEST_FILE = 'test/data/zipWithEncryption.zip' + INPUT_FILE1 = 'test/data/file1.txt' + + def test_encrypt + test_file = open(ENCRYPT_ZIP_TEST_FILE, 'rb').read + + @rand = [250, 143, 107, 13, 143, 22, 155, 75, 228, 150, 12] + @output = ::Zip::DOSTime.stub(:now, ::Zip::DOSTime.new(2014, 12, 17, 15, 56, 24)) do + Random.stub(:rand, lambda { |range| @rand.shift }) do + Zip::OutputStream.write_buffer(::StringIO.new(''), Zip::TraditionalEncrypter.new('password')) do |zos| + zos.put_next_entry('file1.txt') + zos.write open(INPUT_FILE1).read + end.string + end + end + + @output.unpack("C*").each_with_index do |c, i| + assert_equal test_file[i].ord, c + end + end + + def test_decrypt + Zip::InputStream.open(ENCRYPT_ZIP_TEST_FILE, 0, Zip::TraditionalDecrypter.new('password')) do |zis| + entry = zis.get_next_entry + assert_equal 'file1.txt', entry.name + assert_equal 1327, entry.size + assert_equal open(INPUT_FILE1, 'r').read, zis.read + end + end +end