From e62acf1be139ae47c89798d339b06207f0cd8f78 Mon Sep 17 00:00:00 2001 From: KMDMNAK Date: Mon, 20 Sep 2021 10:31:03 +0900 Subject: [PATCH 1/8] add go mod --- go.mod | 8 ++++++++ go.sum | 10 ++++++++++ 2 files changed, 18 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3e90e96 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/KMDMNAK/zip + +go 1.16 + +require ( + 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..c8e3575 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +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= From 22860b4e9c8d4b4307018404a5beada145959d9c Mon Sep 17 00:00:00 2001 From: KMDMNAK Date: Mon, 20 Sep 2021 10:32:05 +0900 Subject: [PATCH 2/8] fix test for getting file name using function. --- crypto_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_test.go b/crypto_test.go index cc5e2de..6e95af5 100644 --- a/crypto_test.go +++ b/crypto_test.go @@ -56,7 +56,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() From 604647f2419ce9b1d292303917bccc3bffd14739 Mon Sep 17 00:00:00 2001 From: KMDMNAK Date: Mon, 20 Sep 2021 16:06:35 +0900 Subject: [PATCH 3/8] format by vscode --- crypto.go | 2 +- zipcrypto.go | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) 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/zipcrypto.go b/zipcrypto.go index 309bc32..8f3dc93 100644 --- a/zipcrypto.go +++ b/zipcrypto.go @@ -4,11 +4,12 @@ import ( "io" "bytes" "hash/crc32" + "io" ) type ZipCrypto struct { password []byte - Keys [3]uint32 + Keys [3]uint32 } func NewZipCrypto(passphrase []byte) *ZipCrypto { @@ -55,7 +56,7 @@ 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 } @@ -63,7 +64,7 @@ func (z *ZipCrypto) Decrypt(chiper []byte) []byte { } 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) { @@ -102,8 +103,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 +} From f217df9b150c20ea58324d4bab89e1f98c714e41 Mon Sep 17 00:00:00 2001 From: KMDMNAK Date: Mon, 20 Sep 2021 16:07:56 +0900 Subject: [PATCH 4/8] make key update oneline --- zipcrypto.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/zipcrypto.go b/zipcrypto.go index 8f3dc93..9313a1b 100644 --- a/zipcrypto.go +++ b/zipcrypto.go @@ -30,10 +30,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)*134775813 + 1 + z.Keys[2] = crc32update(z.Keys[2], (byte)(z.Keys[1]>>24)) } func (z *ZipCrypto) magicByte() byte { From 0110543f1dfc806da2fc2c9ae0f1825e92abf352 Mon Sep 17 00:00:00 2001 From: komada Date: Fri, 8 Oct 2021 00:51:56 +0900 Subject: [PATCH 5/8] rename findBodyOffset to getHeaderLength --- reader.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/reader.go b/reader.go index 48a7c17..2b1ddaa 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,7 +134,7 @@ 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 } @@ -227,9 +227,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 From 0f5fac51caf7fbf56a3eaabe5802f8430f867cf5 Mon Sep 17 00:00:00 2001 From: komada Date: Sat, 9 Oct 2021 10:18:57 +0900 Subject: [PATCH 6/8] change coef for updating keys. --- zipcrypto.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zipcrypto.go b/zipcrypto.go index 9313a1b..636bc46 100644 --- a/zipcrypto.go +++ b/zipcrypto.go @@ -31,7 +31,7 @@ func (z *ZipCrypto) init() { func (z *ZipCrypto) updateKeys(byteValue byte) { z.Keys[0] = crc32update(z.Keys[0], byteValue) - z.Keys[1] = (z.Keys[1]+z.Keys[0]&0xff)*134775813 + 1 + z.Keys[1] = (z.Keys[1]+z.Keys[0]&0xff)*0x8088405 + 1 z.Keys[2] = crc32update(z.Keys[2], (byte)(z.Keys[1]>>24)) } From 72db78ff3e298a596c92b3bc8b51ff1cbdbfcf4d Mon Sep 17 00:00:00 2001 From: komada Date: Sat, 9 Oct 2021 10:20:39 +0900 Subject: [PATCH 7/8] Add support for standard password validation. --- crypto_test.go | 48 ++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ reader.go | 3 +-- testdata/password-abc.zip | Bin 0 -> 197 bytes zipcrypto.go | 37 ++++++++++++++++++++++++----- 6 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 testdata/password-abc.zip diff --git a/crypto_test.go b/crypto_test.go index 6e95af5..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) { diff --git a/go.mod b/go.mod index 3e90e96..861aa6a 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ 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 index c8e3575..a6b0ba6 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +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= diff --git a/reader.go b/reader.go index 2b1ddaa..712f03e 100644 --- a/reader.go +++ b/reader.go @@ -147,8 +147,7 @@ func (f *File) Open() (rc io.ReadCloser, err error) { 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 { } } else if r, err = newDecryptionReader(rr, f); err != nil { return diff --git a/testdata/password-abc.zip b/testdata/password-abc.zip new file mode 100644 index 0000000000000000000000000000000000000000..0c2418a643c5872960ab5db421eb94f90ffce8e0 GIT binary patch literal 197 zcmWIWW@h1H;ACK6=!w(`cHX!B1uu}z2*jKWG7KrDxw(~kB^4#1A)E}%97(o`;mNj% zr4`%^j4Ush85qC>$7fakC*184AKB$KECRgQIgm{W@MdI^W5#8J1kfIcZT}lVEF}9_ XA@-r!72wUv2GPRE;16UZfH({QOQ9x4 literal 0 HcmV?d00001 diff --git a/zipcrypto.go b/zipcrypto.go index 636bc46..0e226cc 100644 --- a/zipcrypto.go +++ b/zipcrypto.go @@ -1,15 +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 { @@ -62,14 +65,36 @@ func (z *ZipCrypto) Decrypt(chiper []byte) []byte { 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) } -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) From 9805a298b536e77cd83ceb755164dd5a75c65c4c Mon Sep 17 00:00:00 2001 From: komada Date: Sat, 9 Oct 2021 10:20:55 +0900 Subject: [PATCH 8/8] Make file open return error --- reader.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/reader.go b/reader.go index 712f03e..245c891 100644 --- a/reader.go +++ b/reader.go @@ -136,7 +136,7 @@ func (f *File) DataOffset() (offset int64, err error) { func (f *File) Open() (rc io.ReadCloser, err error) { 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,12 +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); err != nil { + return nil, err } } else if r, err = newDecryptionReader(rr, f); err != nil { - return + return nil, err } } else { r = rr @@ -158,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() { @@ -175,7 +175,7 @@ func (f *File) Open() (rc io.ReadCloser, err error) { f: f, desr: desr, } - return + return rc, nil } type checksumReader struct {