From 589f90f24e8be146e528b8a1a028f88af315c355 Mon Sep 17 00:00:00 2001 From: farmerx Date: Tue, 11 Dec 2018 17:16:34 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=A0=87=E5=87=86?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E5=92=8Caes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 92 +++++++++++++++++++++++++ README.txt | 77 --------------------- crypto.go | 32 +++++++-- crypto_test.go | 82 +++++++++++++++------- example_test.go | 4 +- reader.go | 7 +- reader_test.go | 0 register.go | 0 struct.go | 1 + testdata/crc32-not-streamed.zip | Bin 314 -> 0 bytes testdata/dd.zip | Bin 154 -> 0 bytes testdata/go-no-datadesc-sig.zip | Bin 330 -> 0 bytes testdata/go-with-datadesc-sig.zip | Bin 242 -> 0 bytes testdata/gophercolor16x16.png | Bin 785 -> 0 bytes testdata/hello-aes.zip | Bin 215 -> 0 bytes testdata/macbeth-act1.zip | Bin 10057 -> 0 bytes testdata/readme.notzip | Bin 1906 -> 0 bytes testdata/readme.zip | Bin 1886 -> 0 bytes testdata/symlink.zip | Bin 173 -> 0 bytes testdata/test-trailing-junk.zip | Bin 1184 -> 0 bytes testdata/test.zip | Bin 1170 -> 0 bytes testdata/unix.zip | Bin 620 -> 0 bytes testdata/winxp.zip | Bin 412 -> 0 bytes testdata/world-aes.zip | Bin 392 -> 0 bytes testdata/zip64-2.zip | Bin 266 -> 0 bytes testdata/zip64.zip | Bin 242 -> 0 bytes writer.go | 27 +++++--- writer_test.go | 0 zip_test.go | 0 zipcrypto.go | 109 ++++++++++++++++++++++++++++++ zipwriters.png | Bin 31 files changed, 311 insertions(+), 120 deletions(-) create mode 100755 README.md delete mode 100644 README.txt mode change 100644 => 100755 crypto.go mode change 100644 => 100755 crypto_test.go mode change 100644 => 100755 example_test.go mode change 100644 => 100755 reader.go mode change 100644 => 100755 reader_test.go mode change 100644 => 100755 register.go mode change 100644 => 100755 struct.go delete mode 100644 testdata/crc32-not-streamed.zip delete mode 100644 testdata/dd.zip delete mode 100644 testdata/go-no-datadesc-sig.zip delete mode 100644 testdata/go-with-datadesc-sig.zip delete mode 100644 testdata/gophercolor16x16.png delete mode 100644 testdata/hello-aes.zip delete mode 100644 testdata/macbeth-act1.zip delete mode 100644 testdata/readme.notzip delete mode 100644 testdata/readme.zip delete mode 100644 testdata/symlink.zip delete mode 100644 testdata/test-trailing-junk.zip delete mode 100644 testdata/test.zip delete mode 100644 testdata/unix.zip delete mode 100644 testdata/winxp.zip delete mode 100644 testdata/world-aes.zip delete mode 100644 testdata/zip64-2.zip delete mode 100644 testdata/zip64.zip mode change 100644 => 100755 writer.go mode change 100644 => 100755 writer_test.go mode change 100644 => 100755 zip_test.go create mode 100755 zipcrypto.go mode change 100644 => 100755 zipwriters.png diff --git a/README.md b/README.md new file mode 100755 index 0000000..f4fdcde --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +This fork add support for Standard Zip Encryption. + +The work is based on https://github.com/alexmullins/zip + +Available encryption: + +``` +zip.StandardEncryption +zip.AES128Encryption +zip.AES192Encryption +zip.AES256Encryption +``` + +## Warning + +Zip Standard Encryption isn't actually secure. +Unless you have to work with it, please use AES encryption instead. + +## Example Encrypt Zip + +``` +package main + +import ( + "bytes" + "io" + "log" + "os" + + "github.com/yeka/zip" +) + +func main() { + contents := []byte("Hello World") + fzip, err := os.Create(`./test.zip`) + if err != nil { + log.Fatalln(err) + } + zipw := zip.NewWriter(fzip) + defer zipw.Close() + w, err := zipw.Encrypt(`test.txt`, `golang`, zip.AES256Encryption) + if err != nil { + log.Fatal(err) + } + _, err = io.Copy(w, bytes.NewReader(contents)) + if err != nil { + log.Fatal(err) + } + zipw.Flush() +} +``` + +## Example Decrypt Zip + +``` +package main + +import ( + "fmt" + "io/ioutil" + "log" + + "github.com/yeka/zip" +) + +func main() { + r, err := zip.OpenReader("encrypted.zip") + if err != nil { + log.Fatal(err) + } + defer r.Close() + + for _, f := range r.File { + if f.IsEncrypted() { + f.SetPassword("12345") + } + + r, err := f.Open() + if err != nil { + log.Fatal(err) + } + + buf, err := ioutil.ReadAll(r) + if err != nil { + log.Fatal(err) + } + defer r.Close() + + fmt.Printf("Size of %v: %v byte(s)\n", f.Name, len(buf)) + } +} +``` diff --git a/README.txt b/README.txt deleted file mode 100644 index 24a6265..0000000 --- a/README.txt +++ /dev/null @@ -1,77 +0,0 @@ -This is a fork of the Go archive/zip package to add support -for reading/writing password protected .zip files. -Only supports Winzip's AES extension: http://www.winzip.com/aes_info.htm. - -This package DOES NOT intend to implement the encryption methods -mentioned in the original PKWARE spec (sections 6.0 and 7.0): -https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT - -Status - Alpha. More tests and code clean up next. - -Documentation - -https://godoc.org/github.com/alexmullins/zip - -Roadmap -======== -Reading - Done. -Writing - Done. -Testing - Needs more. - -The process -============ -1. hello.txt -> compressed -> encrypted -> .zip -2. .zip -> decrypted -> decompressed -> hello.txt - -WinZip AES specifies -===================== -1. Encryption-Decryption w/ AES-CTR (128, 192, or 256 bits) -2. Key generation with PBKDF2-HMAC-SHA1 (1000 iteration count) that -generates a master key broken into the following: - a. First m bytes is for the encryption key - b. Next n bytes is for the authentication key - c. Last 2 bytes is the password verification value. -3. Following salt lengths are used w/ password during keygen: - ------------------------------ - AES Key Size | Salt Size - ------------------------------ - 128bit(16bytes) | 8 bytes - 192bit(24bytes) | 12 bytes - 256bit(32bytes) | 16 bytes - ------------------------------- -4. Master key len = AESKeyLen + AuthKeyLen + PWVLen: - a. AES 128 = 16 + 16 + 2 = 34 bytes of key material - b. AES 192 = 24 + 24 + 2 = 50 bytes of key material - c. AES 256 = 32 + 32 + 2 = 66 bytes of key material -5. Authentication Key is same size as AES key. -6. Authentication with HMAC-SHA1-80 (truncated to 80bits). -7. A new master key is generated for every file. -8. The file header and directory header compression method will -be 99 (decimal) indicating Winzip AES encryption. The actual -compression method will be in the extra's payload at the end -of the headers. -9. A extra field will be added to the file header and directory -header identified by the ID 0x9901 and contains the following info: - a. Header ID (2 bytes) - b. Data Size (2 bytes) - c. Vendor Version (2 bytes) - d. Vendor ID (2 bytes) - e. AES Strength (1 byte) - f. Compression Method (2 bytes) -10. The Data Size is always 7. -11. The Vendor Version can be either 0x0001 (AE-1) or -0x0002 (AE-2). -12. Vendor ID is ASCII "AE" -13. AES Strength: - a. 0x01 - AES-128 - b. 0x02 - AES-192 - c. 0x03 - AES-256 -14. Compression Method is the actual compression method -used that was replaced by the encryption process mentioned in #8. -15. AE-1 keeps the CRC and should be verified after decompression. -AE-2 removes the CRC and shouldn't be verified after decompression. -Refer to http://www.winzip.com/aes_info.htm#winzip11 for the reasoning. -16. Storage Format (file data payload totals CompressedSize64 bytes): - a. Salt - 8, 12, or 16 bytes depending on keysize - b. Password Verification Value - 2 bytes - c. Encrypted Data - compressed size - salt - pwv - auth code lengths - d. Authentication code - 10 bytes diff --git a/crypto.go b/crypto.go old mode 100644 new mode 100755 index 137b674..82a7202 --- a/crypto.go +++ b/crypto.go @@ -19,7 +19,14 @@ import ( "golang.org/x/crypto/pbkdf2" ) +type EncryptionMethod int + const ( + StandardEncryption EncryptionMethod = 1 + AES128Encryption EncryptionMethod = 2 + AES192Encryption EncryptionMethod = 3 + AES256Encryption EncryptionMethod = 4 + // AES key lengths aes128 = 16 aes192 = 24 @@ -379,18 +386,20 @@ func encryptStream(key []byte, w io.Writer) (io.Writer, error) { // newEncryptionWriter returns an io.Writer that when written to, 1. writes // out the salt, 2. writes out pwv, and 3. writes out authenticated, encrypted // data. The authcode will be written out in fileWriter.close(). -func newEncryptionWriter(w io.Writer, password passwordFn, fw *fileWriter) (io.Writer, error) { - var salt [16]byte +func newEncryptionWriter(w io.Writer, password passwordFn, fw *fileWriter, aesstrength byte) (io.Writer, error) { + keysize := aesKeyLen(aesstrength) + salt := make([]byte, keysize/2) _, err := rand.Read(salt[:]) if err != nil { return nil, errors.New("zip: unable to generate random salt") } - ekey, akey, pwv := generateKeys(password(), salt[:], aes256) + ekey, akey, pwv := generateKeys(password(), salt[:], keysize) fw.hmac = hmac.New(sha1.New, akey) aw := &authWriter{ hmac: fw.hmac, w: w, } + es, err := encryptStream(ekey, aw) if err != nil { return nil, err @@ -424,11 +433,23 @@ func (h *FileHeader) writeWinZipExtra() { eb.uint16(7) // following data size is 7 eb.uint16(2) // ae 2 eb.uint16(0x4541) // "AE" - eb.uint8(3) // aes256 + eb.uint8(h.aesStrength) // aes256 eb.uint16(h.Method) // original compression method h.Extra = append(h.Extra, buf[:]...) } +func (h *FileHeader) SetEncryptionMethod(enc EncryptionMethod) { + h.encryption = enc + switch enc { + case AES128Encryption: + h.aesStrength = 1 + case AES192Encryption: + h.aesStrength = 2 + case AES256Encryption: + h.aesStrength = 3 + } +} + func (h *FileHeader) setEncryptionBit() { h.Flags |= 0x1 } @@ -452,11 +473,12 @@ type passwordFn func() []byte // contents will be encrypted with AES-256 using the given password. The // file's contents must be written to the io.Writer before the next call // to Create, CreateHeader, or Close. -func (w *Writer) Encrypt(name string, password string) (io.Writer, error) { +func (w *Writer) Encrypt(name string, password string, enc EncryptionMethod) (io.Writer, error) { fh := &FileHeader{ Name: name, Method: Deflate, } fh.SetPassword(password) + fh.SetEncryptionMethod(enc) return w.CreateHeader(fh) } diff --git a/crypto_test.go b/crypto_test.go old mode 100644 new mode 100755 index e7b7277..cc5e2de --- a/crypto_test.go +++ b/crypto_test.go @@ -175,9 +175,55 @@ func TestPasswordWriteSimple(t *testing.T) { contents := []byte("Hello World") conLen := len(contents) + for _, enc := range []EncryptionMethod{StandardEncryption, AES128Encryption, AES192Encryption, AES256Encryption} { + raw := new(bytes.Buffer) + zipw := NewWriter(raw) + w, err := zipw.Encrypt("hello.txt", "golang", enc) + if err != nil { + t.Errorf("Expected to create a new FileHeader") + } + n, err := io.Copy(w, bytes.NewReader(contents)) + if err != nil || n != int64(conLen) { + t.Errorf("Expected to write the full contents to the writer.") + } + zipw.Close() + + // Read the zip + buf := new(bytes.Buffer) + zipr, err := NewReader(bytes.NewReader(raw.Bytes()), int64(raw.Len())) + if err != nil { + t.Errorf("Expected to open a new zip reader: %v", err) + } + nn := len(zipr.File) + if nn != 1 { + t.Errorf("Expected to have one file in the zip archive, but has %d files", nn) + } + z := zipr.File[0] + z.SetPassword("golang") + rr, err := z.Open() + if err != nil { + t.Errorf("Expected to open the readcloser: %v", err) + } + n, err = io.Copy(buf, rr) + if err != nil { + t.Errorf("Expected to write to temporary buffer: %v", err) + } + if n != int64(conLen) { + t.Errorf("Expected to copy %d bytes to temp buffer, but copied %d bytes instead", conLen, n) + } + if !bytes.Equal(contents, buf.Bytes()) { + t.Errorf("Expected the unzipped contents to equal '%s', but was '%s' instead", contents, buf.Bytes()) + } + } +} + +func TestZipCrypto(t *testing.T) { + contents := []byte("Hello World") + conLen := len(contents) + raw := new(bytes.Buffer) zipw := NewWriter(raw) - w, err := zipw.Encrypt("hello.txt", "golang") + w, err := zipw.Encrypt("hello.txt", "golang", StandardEncryption) if err != nil { t.Errorf("Expected to create a new FileHeader") } @@ -187,30 +233,14 @@ func TestPasswordWriteSimple(t *testing.T) { } zipw.Close() - // Read the zip - buf := new(bytes.Buffer) - zipr, err := NewReader(bytes.NewReader(raw.Bytes()), int64(raw.Len())) - if err != nil { - t.Errorf("Expected to open a new zip reader: %v", err) - } - nn := len(zipr.File) - if nn != 1 { - t.Errorf("Expected to have one file in the zip archive, but has %d files", nn) - } - z := zipr.File[0] - z.SetPassword("golang") - rr, err := z.Open() - if err != nil { - t.Errorf("Expected to open the readcloser: %v", err) - } - n, err = io.Copy(buf, rr) - if err != nil { - t.Errorf("Expected to write to temporary buffer: %v", err) - } - if n != int64(conLen) { - t.Errorf("Expected to copy %d bytes to temp buffer, but copied %d bytes instead", conLen, n) - } - if !bytes.Equal(contents, buf.Bytes()) { - t.Errorf("Expected the unzipped contents to equal '%s', but was '%s' instead", contents, buf.Bytes()) + zipr, _ := NewReader(bytes.NewReader(raw.Bytes()), int64(raw.Len())) + zipr.File[0].SetPassword("golang") + r, _ := zipr.File[0].Open() + res := new(bytes.Buffer) + io.Copy(res, r) + r.Close() + + if !bytes.Equal(contents, res.Bytes()) { + t.Errorf("Expected the unzipped contents to equal '%s', but was '%s' instead", contents, res.Bytes()) } } diff --git a/example_test.go b/example_test.go old mode 100644 new mode 100755 index d625b7e..dce27ee --- a/example_test.go +++ b/example_test.go @@ -11,7 +11,7 @@ import ( "log" "os" - "github.com/alexmullins/zip" + "github.com/yeka/zip" ) func ExampleWriter() { @@ -81,7 +81,7 @@ func ExampleWriter_Encrypt() { // write a password zip raw := new(bytes.Buffer) zipw := zip.NewWriter(raw) - w, err := zipw.Encrypt("hello.txt", "golang") + w, err := zipw.Encrypt("hello.txt", "golang", zip.AES256Encryption) if err != nil { log.Fatal(err) } diff --git a/reader.go b/reader.go old mode 100644 new mode 100755 index a9e3f6b..48a7c17 --- a/reader.go +++ b/reader.go @@ -145,7 +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 r, err = newDecryptionReader(rr, f); err != nil { + + if f.ae == 0 { + if r, err = ZipCryptoDecryptor(rr, f.password()); err != nil { + return + } + } else if r, err = newDecryptionReader(rr, f); err != nil { return } } else { diff --git a/reader_test.go b/reader_test.go old mode 100644 new mode 100755 diff --git a/register.go b/register.go old mode 100644 new mode 100755 diff --git a/struct.go b/struct.go old mode 100644 new mode 100755 index 1e0cba2..7e50a5c --- a/struct.go +++ b/struct.go @@ -101,6 +101,7 @@ type FileHeader struct { // https://www.imperialviolet.org/2015/05/16/aeads.html DeferAuth bool + encryption EncryptionMethod password passwordFn // Returns the password to use when reading/writing ae uint16 aesStrength byte diff --git a/testdata/crc32-not-streamed.zip b/testdata/crc32-not-streamed.zip deleted file mode 100644 index f268d88732f837723525285c0922231d9c3fcb46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 314 zcmWIWW@h1H0D;u@42Kn|Ms+MeHVCsb$S|bk=j)YJl!S(GGBDo@jr0fM(h6<{MwYLP zKvg0@Wk4ld0dPaofQG!>yod$akfg*SxFHXK27oY{AwVTSLl~Llm~pv90%#Qj1JF{2 wC5<2!+-0l~m!TPmY#64SkPUMM8U}YE&@e2n3-D%T1KG(0gtLHj7l^|E0B`I%6#xJL diff --git a/testdata/dd.zip b/testdata/dd.zip deleted file mode 100644 index e53378b0b0e56203a08c139b83c67a9b918c0b81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154 zcmWIWW@Zs#-~hr?8BTT#NPq*#PRqSdnT}#{y)7Fgt?;Lt1{mUP(nsXb39<^S#hW|I!L>21b^z zj6fA4KqWwFE@UkMaKqAohP~Xphy~fOq{Je)VGl4314?rtYYFgXWD;S_DNk{CvHViV`5j72wUzu_D!|js++T!U1?SCM6buGy*BG#+REH zu^==uGKnxC>_j#cWG55r00004XF*Lt007q5 z)K6G40000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!qe(a@st@4BKo_oJPzw>?HIhVm0L#Ej6 zcCguOm(QI#dLysUSQM1xW#7b?WhI9coK{!icDucq@rulkmX=oZy1mWeXsEqpG#cQz z^?utRB)7Nn2_^KAYm5|#yEXazMjH4t8k!0_NeXY&iE#p@M7{5k^WvsIwt5X66u zgMz_u`%zcjR>QW&SS(65Ye^9(fMuCu1-f+|cW+i$cI({q=d?f-4KkbL8H||JduEK zWEsv^R+I=c^Vc^Q6f|YEz%UF)-b=4uIMaXYiY0$xaS0d}!L(ln-P#;bsW@OrfJ(`M zqjL}(Z9R?iQmBps*cDKe=-Au$LmfdmOt7Tycmj$y<|dN@QaA~tlBZF^f-Lp@sesL# z=v+{~^TgRR$*YtpDuIysfOkp+VMc;zEC#y#TrZp+NA%AD{$()=e}R z3@I8(`tF+qL{{dnWNKppLokAoTC?UfF*Tt`)9K%C@(KhS0IXFLXxD~r|ta@O^5g7&dyB3xG)uXH!$3b5r40` zx_Z)TwPycJf*@p8yGZo8%QIme`{s}EJpZ7(yZbY-h4gwoy>wZv{T5&Vs0a;<1LtDu P00000NkvXXu0mjff_q_+ diff --git a/testdata/hello-aes.zip b/testdata/hello-aes.zip deleted file mode 100644 index 341a408e476c477568ea5fcb6da74a85b690bbc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215 zcmWIWW@a&FU}Q*U*tR9godF0mftVMFIT^SaGE#GL^7Tq8N*HIdGcYkYx-tXBJGUgW z?d@-xytp&bc8A)<#m^q>YR|bSw(S1I^YvXKJLW4to_SjQYN}L#HzSih11`h$L3ROw w0+?h#Sj7buVq}nDC_S+0T86+y#$D!DFCx=O&I#~lWdj+(2!v@sIvQ*q0Jjb_VE_OC diff --git a/testdata/macbeth-act1.zip b/testdata/macbeth-act1.zip deleted file mode 100644 index 8ee2aa23983d173c28e4330c7282cc0fe542eb63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10057 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 VqOe# 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 P2mJj{j{FCh0|5RH-20fc diff --git a/testdata/readme.notzip b/testdata/readme.notzip deleted file mode 100644 index 81737275c6ebf5ea69b992753ab4050f031f31b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1906 zcmZ{kdr(wW9LF#2BCy+<&KTnWqeoDYf?diGWm*(rdC0=9fSQEX-QBahSMT1l-p4LT zi4V-tcod2+1|0)s(nQGY57(e##v~0b6;fv~ZB%?PjxVCeSm)ejE3NiD`}^JBw$b3;E)Jtu!?a5JHtYiIai?^Pf=7T1>TI-|ELOGw`deaWh*IaG!;b^7}7HG3=50G zC`E2}g|Y7OUZYg1rh=3M3W}0HTt#`N%X7zme(&%OMM2tpHa{wQ<(j%t1(RPmn_j!3 zoJGF=_Mw=`e{{@P;j5nCG(F@@Lo(`*ym=@&XgIRGa&C8DXi`$>#4Odcx;gW5Q@7s> z%QQ9ib-h&*vdE{sHAYSC>qy+cH$^k>Va>Zy1DgvfcaIqxR;6C>hWb*{`|Hey&i5zI zdGvBk!zXvM2UHK1h8bZ_@>h#{stuDuPQ|-zo7`WhUNmi7(_y~7zj|PMSael)aL7?l zTXHF-={fIwIBk8+xX8zAeJSfqJvkLwB^w?WH!nr4+QZuV0lT`g)}VIwx<2attYz$s z*59{pyZ(+hJe|7csYpETiwSQlj%w5+P1D@+$`h3XdG#5P*W~wK>F7U{{#vo})WKQ% znN0^Ro3?hQuI^9m_rzsnNA3Fghql5C z=TCgIYfDeJdE;|eUsYsIE&Oh`*22luexY~GRPS-v4} z`^61)>dl7hM>P84-{Ne^n{U;B>D5<-FMcoc?b>C1<6-}@G*hS%SV`6YDAfjb*+fBItw|TJkGEX|SKtX9zXt29G9LLHKFJ-`gT z8dWCFvLUPFf}BW;4B`N7L6QiZ$O}XS7)}srmIX9t1Cjti7K+!XJPd9DoTBI!Qgk{8 zA|W!_F~Q$jbY;XiARGk=31mEd%FM)>i5eA|9xFvE;>k%C ziQ?H=WTjar5=1#s_iSBx#DyFS1py#jfcG+31AGgDw)lXyBkGg8kpO5O0vjWUJY$jM zNhQvP_aiRNE6hj`#4;8F7VpF1I(CGkL>Uq`=A;|)3k~vJO>UczK=hf%a5VNZBT$nV zl#lxhB$#8DrE=092$oBN2qf$w&qEs)&EmfhoB!85_*%cV9K&(Ib;=9DZ`fyq=v5=o9-!Wg(|(Fu@U{=5jv=Eg7*DxiCFA9fK6;#rQXoI5%xw0dSb%a9f=u@P3OhWbp*9kvtw+B!j{ z;a>p%fB++_wa|hSOcx+_{u)t&(!HFami^U2E&^8$n%wIsdzjqr^LWecQdLci_ zf)lcAtRPDw8%9nNETvs;7RCn#g{2}-TQLc;V%QwnU;lPfvQ2pgc`?Gfi*Y!y8*Y5` zWVI2?A}=8|aB{Tx5EIB!i1PqHV5uANBJjJU4^(?9W=<>0)=wdyDNpU7Q5F7&Eb<-0 Ng$TkBTgVF9KLIxg!L9%R diff --git a/testdata/readme.zip b/testdata/readme.zip deleted file mode 100644 index 5642a67e77d5f5a45c92ea701801ae0b993f4724..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1886 zcmZ{kdrVVT9LF!V3}{!Ui6aJx9zjJmXxSX1WJM9mLkg{cIvKj%wx{jY+k5JLv@kYN z!5JNo4aFBh<3P!DBFwoztb=VNPNqXQ6{aRQT~vG!dePQq(h46jfkI%g8e zQVuF8O1`*?@=TZJjr;uWq3w!-v;}N_RP?Gf^!Rp8n9Jq|ixOs_FG}=jWzwyBn5iYU=NPvm|7(PkVE$ zn%di$xNlF2X7Iz>ccTV36;$mSJ1(qRz3_GQ#iaMwnGc>DNSyodrP{_%?qmn>Ym_`BcAr;QcBZ%&iP<^W$pOL zM{9j4>rA~l6iev9+|0J$w{5-ljyF7=y6UM& zJm!lDZ!eB&(j!gtyz;8!RfBmA8IafH4_xjXIGFxwvGU}B+54D{2P_-6bfvByNbTZg zYEG9NYB-vdFO+)KO}6`|nkx(HT*IyXr^ zb^HgqOO3wPq2SAXb#rneMv5o4JvP34HlnBI=;Pp$q}mNz<3{4bQ};JSovnWG+B9qA z`L`nMm%5od^-K4rKX)VOUiZCa6BcZ#+EovCv*1kL;g;FHVQ zY{{E$Hhk&TSBEcoFZ1oX<^AJhEn}y}rX+ZV+siLrZ&J?L-d4B5xQgHL@!m?d<5)~^ z*Z%K)3sobPcVm7D-Ms(y`dgnq9J)SqM@h-LjT^3s4&Dr}Qa8&AGwPkgOp5G~&;#m~W^oaxwzIFVP8sfF#}u zffd;x;Gr7{jEML$U`M5NQ(^O z0Bu2%2%N|ZLGzUBk^a2`ij1{u-J}z1m zp-rRuS9wbr;3bYD1sn)RK|%r2p8bJ4Ay`{c!>CbwxjCX8Jq=Z9s(O9h&*GFzGk2y8VRA4d{ zkwlVXmoNryT66+rmw#{|Yy`tYt4!Mrl7l#cAs&K}tq=*Me!Al@Zc9gPMJ`MaPRAex zdNKY@B+gA+R{|VnINVkz3B2DT3|TyZYc!9C7Kvdo2zDO1M)?=OKOn%!YAv+j1TzGP z9eIkzI4m$3MsOmD#bU7n0<=v=1oIb=?(f}3mC#{Lv0lhevfzX)8!O0?$cB-V1WRd` zn}zX#L1C%L(^gD^tQa;&_Se7N)NE6pL0*jT?qVEH?1md3Jy~tUvdBwF4V)Y;KEwo$ x6yiL<4_N9(ya@a*=>yfCh?&!hvh`ERHRXvNG^)b?kVXE-a3O*)#1^uG_D`k1y14)V diff --git a/testdata/symlink.zip b/testdata/symlink.zip deleted file mode 100644 index af846938cde293ccc3dfb310fdfbda641382dd3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWIWW@h1H00D{l&JGuU&FkX?vO$=gL588YGB+nPFFQ1ZlYv@nk^pZ;COKwYW=Vjo0E7PvK@{9%R*1=HrUrPkvVoK_0--OE_5yJj E01XfzDgXcg diff --git a/testdata/test-trailing-junk.zip b/testdata/test-trailing-junk.zip deleted file mode 100644 index 42281b4e3053eec7924b58d234d04e492c3baa38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1184 zcmWIWW@Zs#U|`^2XiQYKJ#hW)VM!oQ3M?YSP?B0)qE}K;5*otEz+CvJ$)^m6ODnh; z7+JnDGBAL3a-Te*6UMN}rFGJkM?wmLfr}l&aaaM)^pwV1FgBTd*)~VY5GrSri z$jrb1!XgYZ4C(m=8L36d`8oMThGrFpW_ksA>0oQD44Qqcff&u2&Hz7mUM?w+fxMm` zE&U=x?Zy@V2qPe0vcxr_Bsf2F!C6nVlf&(QE?5{rm?pSGb*exy9+#I&RurKk- z+m)0yRCPG=d-HHDZeo&8l5*8w*j(kQ)h25CKV`;*MHBS|=I94cI>i2RTbstM*llm) zO8IWizic_XckQR<_w%av+wcEed*5>UK?&z&H{O(96e0boH{+WCE(bu95f2^3EZ4O=VNZ?3)UvtyJg5QDRcf$^EI~Jki zWtQzJsL_x*GnsW)scd27+nNv&O9`(nP~TLZC!C)&dpmb9Gex+~DL%5m{kC!2{41u2r}!o#L;7=ONQ zE-qq{y7hnokNZ9E^>G%L>tA=N)+ZKnN{AiY63=$`mQ27(iv*7buak|7iv?tIZF@fl zt>|OAAsR5{#f{tFon;*@eKst4U%}L6{!DE$Q;-Gc;z`Q`4>jq0{kyW^?3@=d0sB55 zxoLcI^%PC5#tAeEd>m$^DR^;2N$vDP24)a*LyE?_;6T$0ZaWgUlR!) zO)1Y`?`E^OggslIl9kNpY|u0@YV$J_L(}Dqa{cO(OH|YIR<91PW~;8;CG9SvxT%P1 zf&1?rTi;(l9{BL-LFV7-o3%9b_5H&Yx{{nGExEP)?O%T1_U}*5o%1=o!$7pc+~7{P zVEy`CyQ(f_ZB6^{)1;6Rw%g;&&eO(;vcI48f3W$#VE69d#v5C|RN3w=iCp_vEP-JK z2X8m?^Q2e6G|k}Y>gTe~DWNIAn~_P58CTww04Zev=23TwQdKa(&kYWhQ$ShU>qC|zN%!0JcoK%J6 Y{M_8syb`_Q{M=N9y!^c4R3PF40F)}f^8f$< diff --git a/testdata/test.zip b/testdata/test.zip deleted file mode 100644 index 03890c05d4c12196086a99b7f6f51276156b4394..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1170 zcmWIWW@Zs#U|`^2XiQYKJ#hW)VM!oQ3M?YSP?B0)qE}K;5*otEz+CvJ$)^m6ODnh; z7+JnDGBAL3a-Te*6UMN}rFGJkM?wmLfr}l&aaaM)^pwV1FgBTd*)~VY5GrSri z$jrb1!XgYZ4C(m=8L36d`8oMThGrFpW_ksA>0oQD44Qqcff&u2&Hz7mUM?w+fxMm` zE&U=x?Zy@V2qPe0vcxr_Bsf2F!C6nVlf&(QE?5{rm?pSGb*exy9+#I&RurKk- z+m)0yRCPG=d-HHDZeo&8l5*8w*j(kQ)h25CKV`;*MHBS|=I94cI>i2RTbstM*llm) zO8IWizic_XckQR<_w%av+wcEed*5>UK?&z&H{O(96e0boH{+WCE(bu95f2^3EZ4O=VNZ?3)UvtyJg5QDRcf$^EI~Jki zWtQzJsL_x*GnsW)scd27+nNv&O9`(nP~TLZC!C)&dpmb9Gex+~DL%5m{kC!2{41u2r}!o#L;7=ONQ zE-qq{y7hnokNZ9E^>G%L>tA=N)+ZKnN{AiY63=$`mQ27(iv*7buak|7iv?tIZF@fl zt>|OAAsR5{#f{tFon;*@eKst4U%}L6{!DE$Q;-Gc;z`Q`4>jq0{kyW^?3@=d0sB55 zxoLcI^%PC5#tAeEd>m$^DR^;2N$vDP24)a*LyE?_;6T$0ZaWgUlR!) zO)1Y`?`E^OggslIl9kNpY|u0@YV$J_L(}Dqa{cO(OH|YIR<91PW~;8;CG9SvxT%P1 zf&1?rTi;(l9{BL-LFV7-o3%9b_5H&Yx{{nGExEP)?O%T1_U}*5o%1=o!$7pc+~7{P zVEy`CyQ(f_ZB6^{)1;6Rw%g;&&eO(;vcI48f3W$#VE69d#v5C|RN3w=iCp_vEP-JK z2X8m?^Q2e6G|k}Y>gTe~DWNIAn~_P58CTww04Zev=23TwQdKa(&kYWhQ$ShU>qC|zN%!0JcoK%J6 L{M_8syb?VCG^Dz$ diff --git a/testdata/unix.zip b/testdata/unix.zip deleted file mode 100644 index ce1a981b2806d7e7a4026383622bf033aac426a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 620 zcmWIWW@h1H0D+!>4*T9e!nGVgHVCsa$S`E2=H%puhHx@4ujqc@7dHEWUugw510%}| zW(Ec@QJ!CvlcK=O6#zG8CeWC9v+JtZfJT5YJJ6Vv%p(1y#3Hakhkynd%)&4zEk7T{ z80NqZd!TMO;DQ>Hnp;p(sSh@(t>=Ls2%|X(;glmlr$hT_XGm26ZQ}Xk2 zD#0cQ0Ck_l^i*bUL4Hw5VqOW@MT|^x%(y~G0_;9UAi1p(#DsO=)pfJN@7-m>O3}avrVEFIY2Q>^9azOL2h8n_gnBfL<93z8D<0YVZh)@KY Y1`0(C*Rg`)o`D4j&49t9016@o0NYf3UjP6A diff --git a/testdata/winxp.zip b/testdata/winxp.zip deleted file mode 100644 index 3919322f0c5f8be8f1a214af712b6e86b4d04aef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 412 zcmWIWW@h1H0D+!>4*T9e!nGVgHVCrq~yGK=(+5{uIE^HG#C2X@#4W#Is17f5MpZb3<EG$%FNt?{GyV?yb`dg^b>%*JLMKe)obQ>FfgY#Nc!n~3 zGWsmC$cFihkI1D@-9^IQUv@AAcr!BTGV7w4^dAb?7<7Q*5U`{XL_^GFWDo$`1QG$+ z2m+xYtPIS5f>i@bE4UdLS#~KhF|c$9GXTwJV}qHZ%K)+m0vOTg1SsDFN(1$=gP1Fz Te31G8Z&r}!7+~%L(F_a#G1EK) diff --git a/testdata/zip64.zip b/testdata/zip64.zip deleted file mode 100644 index a2ee1fa33dca48e1ec8dfc7507640bfa09bddeb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 242 zcmWIWW@Zs#U|`^2Feu@2tb6`HQw7KaVKyKRa&>g^b>%*JLMKe)obQ>FfgY#Nc!n~3 zGWsmC$cFihkI1D@-9^IQUv@AAcr!BTGV7w4^dAb?7(g~az>-D~4KbIIK>%zMNCadf u2n2YuvFSjV47xxF1B_4xjP`)?VKh)5J4k2(lDYtIR*)wcVD13X3=9B3ZZ^#T diff --git a/writer.go b/writer.go old mode 100644 new mode 100755 index 27d125e..55b3595 --- a/writer.go +++ b/writer.go @@ -229,14 +229,23 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { // check for password var sw io.Writer = fw.compCount if fh.password != nil { - // we have a password and need to encrypt. - fh.writeWinZipExtra() - fh.Method = 99 // ok to change, we've gotten the comp and wrote extra - ew, err := newEncryptionWriter(sw, fh.password, fw) - if err != nil { - return nil, err + if fh.encryption == StandardEncryption { + ew, err := ZipCryptoEncryptor(sw, fh.password, fw) + if err != nil { + return nil, err + } + sw = ew + } else { + // we have a password and need to encrypt. + fh.writeWinZipExtra() + fh.Method = 99 // ok to change, we've gotten the comp and wrote extra + ew, err := newEncryptionWriter(sw, fh.password, fw, fh.aesStrength) + if err != nil { + //fmt.Println(`HJAHKJHKJHJKHSKJSHKJHKDH`, err) + return nil, err + } + sw = ew } - sw = ew } var err error fw.comp, err = comp(sw) @@ -313,7 +322,7 @@ func (w *fileWriter) close() error { return err } // if encrypted grab the hmac and write it out - if w.header.IsEncrypted() { + if w.header.IsEncrypted() && w.header.encryption != StandardEncryption { authCode := w.hmac.Sum(nil) authCode = authCode[:10] _, err := w.compCount.Write(authCode) @@ -324,7 +333,7 @@ func (w *fileWriter) close() error { // update FileHeader fh := w.header.FileHeader // ae-2 we don't write out CRC - if !fh.IsEncrypted() { + if !fh.IsEncrypted() || fh.encryption == StandardEncryption { fh.CRC32 = w.crc32.Sum32() } fh.CompressedSize64 = uint64(w.compCount.count) diff --git a/writer_test.go b/writer_test.go old mode 100644 new mode 100755 diff --git a/zip_test.go b/zip_test.go old mode 100644 new mode 100755 diff --git a/zipcrypto.go b/zipcrypto.go new file mode 100755 index 0000000..b3d87dc --- /dev/null +++ b/zipcrypto.go @@ -0,0 +1,109 @@ +package zip + +import ( + "bytes" + "hash/crc32" + "io" +) + +type ZipCrypto struct { + password []byte + Keys [3]uint32 +} + +func NewZipCrypto(passphrase []byte) *ZipCrypto { + z := &ZipCrypto{} + z.password = passphrase + z.init() + return z +} + +func (z *ZipCrypto) init() { + z.Keys[0] = 0x12345678 + z.Keys[1] = 0x23456789 + z.Keys[2] = 0x34567890 + + for i := 0; i < len(z.password); i++ { + z.updateKeys(z.password[i]) + } +} + +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)) +} + +func (z *ZipCrypto) magicByte() byte { + var t uint32 = z.Keys[2] | 2 + return byte((t * (t ^ 1)) >> 8) +} + +func (z *ZipCrypto) Encrypt(data []byte) []byte { + length := len(data) + chiper := make([]byte, length) + for i := 0; i < length; i++ { + v := data[i] + chiper[i] = v ^ z.magicByte() + z.updateKeys(v) + } + return chiper +} + +func (z *ZipCrypto) Decrypt(chiper []byte) []byte { + length := len(chiper) + plain := make([]byte, length) + for i, c := range chiper { + v := c ^ z.magicByte() + z.updateKeys(v) + plain[i] = v + } + return plain +} + +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) + b := make([]byte, r.Size()) + + r.Read(b) + + m := z.Decrypt(b) + return io.NewSectionReader(bytes.NewReader(m), 12, int64(len(m))), nil +} + +type zipCryptoWriter struct { + w io.Writer + z *ZipCrypto + first bool + fw *fileWriter +} + +func (z *zipCryptoWriter) Write(p []byte) (n int, err error) { + err = nil + if z.first { + z.first = false + header := []byte{0xF8, 0x53, 0xCF, 0x05, 0x2D, 0xDD, 0xAD, 0xC8, 0x66, 0x3F, 0x8C, 0xAC} + header = z.z.Encrypt(header) + + crc := z.fw.ModifiedTime + header[10] = byte(crc) + header[11] = byte(crc >> 8) + + z.z.init() + z.w.Write(z.z.Encrypt(header)) + n += 12 + } + z.w.Write(z.z.Encrypt(p)) + return +} + +func ZipCryptoEncryptor(i io.Writer, pass passwordFn, fw *fileWriter) (io.Writer, error) { + z := NewZipCrypto(pass()) + zc := &zipCryptoWriter{i, z, true, fw} + return zc, nil +} diff --git a/zipwriters.png b/zipwriters.png old mode 100644 new mode 100755 From 7a2272592fb12d80a182e828c6aeb6900f373ac9 Mon Sep 17 00:00:00 2001 From: Farmerx <90bibin@gmail.com> Date: Tue, 11 Dec 2018 17:19:55 +0800 Subject: [PATCH 2/3] Update README.md --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f4fdcde..ec8c42e 100755 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ This fork add support for Standard Zip Encryption. The work is based on https://github.com/alexmullins/zip Available encryption: - ``` zip.StandardEncryption zip.AES128Encryption @@ -90,3 +89,61 @@ func main() { } } ``` + +### fileheader + +``` + +func StepDealZip(tmpFilesForZip []string, zipFilePath string, job YellowDogJob) (err error) { + archive, err := os.Create(zipFilePath) + if err != nil { + return err + } + defer archive.Close() + zipWriter := zip.NewWriter(archive) + for _, file := range tmpFilesForZip { + fileInfo, err := os.Stat(file) + if err != nil { + return err + } + body, err := ioutil.ReadFile(file) + if err != nil { + return err + } + var filename string + fileNameSplit := strings.Split(fileInfo.Name(), ".") + if fileNameSplit[2] == `pdf` { + filename = "报表_" + fileNameSplit[1] + `.pdf` + } else { + for _, values := range job.RptTableDict { + if values.Dish == fileNameSplit[0] { + filename = fileNameSplit[1] + `_` + values.DishHuman + `.xlsx` + } + } + } + header := &zip.FileHeader{ + Name: filename, + Flags: 1 << 11, // 使用utf8编码 + Method: zip.Deflate, + } + if job.RptPwd != `` { + header.SetPassword(job.RptPwd) + header.SetEncryptionMethod(zip.StandardEncryption) + } + f, err := zipWriter.CreateHeader(header) + if err != nil { + return err + } + _, err = f.Write([]byte(body)) + if err != nil { + return err + } + } + if err = zipWriter.Close(); err != nil { + return err + } + return nil +} + +``` + From 9e9d82c6d492757db51aee74580c90b4227510d2 Mon Sep 17 00:00:00 2001 From: Farmerx <90bibin@gmail.com> Date: Tue, 11 Dec 2018 17:21:25 +0800 Subject: [PATCH 3/3] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec8c42e..9cad5fd 100755 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ func main() { } ``` -### fileheader +### fileheader 设置文件的编码格式 ```