diff --git a/crypto.go b/crypto.go index df23856..a10c2a7 100644 --- a/crypto.go +++ b/crypto.go @@ -388,7 +388,7 @@ func encryptStream(key []byte, w io.Writer) (io.Writer, error) { // data. The authcode will be written out in fileWriter.close(). func newEncryptionWriter(w io.Writer, password passwordFn, fw *fileWriter, aesstrength byte) (io.Writer, error) { keysize := aesKeyLen(aesstrength) - salt := make([]byte, keysize / 2) + salt := make([]byte, keysize/2) _, err := rand.Read(salt[:]) if err != nil { return nil, errors.New("zip: unable to generate random salt") diff --git a/crypto_test.go b/crypto_test.go index cc5e2de..fd9e274 100644 --- a/crypto_test.go +++ b/crypto_test.go @@ -40,6 +40,54 @@ func TestPasswordReadSimple(t *testing.T) { } } +func TestPasswordStandard(t *testing.T) { + type args struct { + fp string + pw string + } + tests := []struct { + name string + wantErr bool + args args + }{ + { + name: "correct password", + args: args{ + fp: "testdata/password-abc.zip", + pw: "abc", + }, + wantErr: false, + }, { + name: "wrong password", + args: args{ + fp: "testdata/password-abc.zip", + pw: "abcd", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Logf("TestName: %s", tt.name) + r, err := OpenReader(tt.args.fp) + if err != nil { + t.Errorf("Expected %s to open: %v.", tt.args.fp, err) + } + defer r.Close() + if len(r.File) != 1 { + t.Errorf("Expected %s to contain one file.", tt.args.fp) + } + f := r.File[0] + f.SetPassword(tt.args.pw) + _, err = f.Open() + if err != nil && !tt.wantErr { + t.Errorf("Expected to open the readcloser: %v.", err) + } + if err == nil && tt.wantErr { + t.Errorf("Expected not to open the readcloser: %v.", err) + } + } +} + // Test for multi-file password protected zip. // Each file can be protected with a different password. func TestPasswordHelloWorldAes(t *testing.T) { @@ -56,7 +104,7 @@ func TestPasswordHelloWorldAes(t *testing.T) { var b bytes.Buffer for _, f := range r.File { if !f.IsEncrypted() { - t.Errorf("Expected %s to be encrypted.", f.FileInfo().Name) + t.Errorf("Expected %s to be encrypted.", f.FileInfo().Name()) } f.SetPassword("golang") rc, err := f.Open() diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..861aa6a --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/KMDMNAK/zip + +go 1.16 + +require ( + github.com/pkg/errors v0.9.1 // indirect + github.com/yeka/zip v0.0.0-20180914125537-d046722c6feb // indirect + golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a6b0ba6 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/yeka/zip v0.0.0-20180914125537-d046722c6feb h1:OJYP70YMddlmGq//EPLj8Vw2uJXmrA+cGSPhXTDpn2E= +github.com/yeka/zip v0.0.0-20180914125537-d046722c6feb/go.mod h1:9BnoKCcgJ/+SLhfAXj15352hTOuVmG5Gzo8xNRINfqI= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 h1:3erb+vDS8lU1sxfDHF4/hhWyaXnhIaO+7RgL4fDZORA= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/reader.go b/reader.go index 48a7c17..245c891 100644 --- a/reader.go +++ b/reader.go @@ -124,7 +124,7 @@ func (rc *ReadCloser) Close() error { // Most callers should instead use Open, which transparently // decompresses data and verifies checksums. func (f *File) DataOffset() (offset int64, err error) { - bodyOffset, err := f.findBodyOffset() + bodyOffset, err := f.getHeaderLength() if err != nil { return } @@ -134,9 +134,9 @@ func (f *File) DataOffset() (offset int64, err error) { // Open returns a ReadCloser that provides access to the File's contents. // Multiple files may be read concurrently. func (f *File) Open() (rc io.ReadCloser, err error) { - bodyOffset, err := f.findBodyOffset() + bodyOffset, err := f.getHeaderLength() if err != nil { - return + return nil, err } // If f is encrypted, CompressedSize64 includes salt, pwvv, encrypted data, // and auth code lengths @@ -145,13 +145,12 @@ func (f *File) Open() (rc io.ReadCloser, err error) { rr := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size) // check for encryption if f.IsEncrypted() { - if f.ae == 0 { - if r, err = ZipCryptoDecryptor(rr, f.password()); err != nil { - return + if r, err = ZipCryptoDecryptor(rr, f); err != nil { + return nil, err } } else if r, err = newDecryptionReader(rr, f); err != nil { - return + return nil, err } } else { r = rr @@ -159,12 +158,12 @@ func (f *File) Open() (rc io.ReadCloser, err error) { dcomp := decompressor(f.Method) if dcomp == nil { err = ErrAlgorithm - return + return nil, err } rc = dcomp(r) // If AE-2, skip CRC and possible dataDescriptor if f.isAE2() { - return + return rc, nil } var desr io.Reader if f.hasDataDescriptor() { @@ -176,7 +175,7 @@ func (f *File) Open() (rc io.ReadCloser, err error) { f: f, desr: desr, } - return + return rc, nil } type checksumReader struct { @@ -227,9 +226,9 @@ func (r *checksumReader) Read(b []byte) (n int, err error) { func (r *checksumReader) Close() error { return r.rc.Close() } -// findBodyOffset does the minimum work to verify the file has a header +// getHeaderLength does the minimum work to verify the file has a header // and returns the file body offset. -func (f *File) findBodyOffset() (int64, error) { +func (f *File) getHeaderLength() (int64, error) { var buf [fileHeaderLen]byte if _, err := f.zipr.ReadAt(buf[:], f.headerOffset); err != nil { return 0, err diff --git a/testdata/password-abc.zip b/testdata/password-abc.zip new file mode 100644 index 0000000..0c2418a Binary files /dev/null and b/testdata/password-abc.zip differ diff --git a/zipcrypto.go b/zipcrypto.go index 309bc32..0e226cc 100644 --- a/zipcrypto.go +++ b/zipcrypto.go @@ -1,14 +1,18 @@ package zip import ( - "io" "bytes" + "encoding/hex" "hash/crc32" + "io" + + "github.com/pkg/errors" ) type ZipCrypto struct { - password []byte - Keys [3]uint32 + password []byte + Keys [3]uint32 + encryptionHeader []byte } func NewZipCrypto(passphrase []byte) *ZipCrypto { @@ -29,10 +33,9 @@ func (z *ZipCrypto) init() { } func (z *ZipCrypto) updateKeys(byteValue byte) { - z.Keys[0] = crc32update(z.Keys[0], byteValue); - z.Keys[1] += z.Keys[0] & 0xff; - z.Keys[1] = z.Keys[1] * 134775813 + 1; - z.Keys[2] = crc32update(z.Keys[2], (byte) (z.Keys[1] >> 24)); + z.Keys[0] = crc32update(z.Keys[0], byteValue) + z.Keys[1] = (z.Keys[1]+z.Keys[0]&0xff)*0x8088405 + 1 + z.Keys[2] = crc32update(z.Keys[2], (byte)(z.Keys[1]>>24)) } func (z *ZipCrypto) magicByte() byte { @@ -55,21 +58,43 @@ func (z *ZipCrypto) Decrypt(chiper []byte) []byte { length := len(chiper) plain := make([]byte, length) for i, c := range chiper { - v := c ^ z.magicByte(); + v := c ^ z.magicByte() z.updateKeys(v) plain[i] = v } return plain } +func (z *ZipCrypto) CheckPasswordVerification(r *io.SectionReader, f *File) error { + encryptionHeader := make([]byte, 12) + _, err := r.Read(encryptionHeader) + if err != nil { + return err + } + r.Seek(-12, 1) + z.encryptionHeader = encryptionHeader + decryptedHeader := z.Decrypt(encryptionHeader) + z.init() + if f.Flags&0x8 > 0 { + if (f.FileHeader.ModifiedTime>>8)&0xff != uint16(decryptedHeader[11]) { + return errors.Errorf("Invalid Password :: Flags: %d, DecryptedHeader: %s, ModifiedTime: %d", f.Flags, hex.EncodeToString(decryptedHeader), (f.FileHeader.ModifiedTime>>8)&0xff) + } + } else if (f.FileHeader.CRC32>>24)&0xff != uint32(decryptedHeader[11]) { + return errors.Errorf("Invalid Password :: Flags: %d, DecryptedHeader: %s, CRC32: %d", f.Flags, hex.EncodeToString(decryptedHeader), (f.FileHeader.CRC32>>24)&0xff) + } + return nil +} + func crc32update(pCrc32 uint32, bval byte) uint32 { - return crc32.IEEETable[(pCrc32 ^ uint32(bval)) & 0xff] ^ (pCrc32 >> 8) + return crc32.IEEETable[(pCrc32^uint32(bval))&0xff] ^ (pCrc32 >> 8) } -func ZipCryptoDecryptor(r *io.SectionReader, password []byte) (*io.SectionReader, error) { - z := NewZipCrypto(password) +func ZipCryptoDecryptor(r *io.SectionReader, f *File) (*io.SectionReader, error) { + z := NewZipCrypto(f.password()) + if err := z.CheckPasswordVerification(r, f); err != nil { + return nil, err + } b := make([]byte, r.Size()) - r.Read(b) m := z.Decrypt(b) @@ -102,8 +127,8 @@ func (z *zipCryptoWriter) Write(p []byte) (n int, err error) { return } -func ZipCryptoEncryptor(i io.Writer, pass passwordFn, fw *fileWriter) (io.Writer, error) { +func ZipCryptoEncryptor(i io.Writer, pass passwordFn, fw *fileWriter) (io.Writer, error) { z := NewZipCrypto(pass()) zc := &zipCryptoWriter{i, z, true, fw} return zc, nil -} \ No newline at end of file +}