diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5934bc --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +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" + "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 3984ca4..0000000 --- a/README.txt +++ /dev/null @@ -1,112 +0,0 @@ -This fork add support for Standard Zip Encryption. -https://github.com/yeka/zip - -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 - -Example Encrypt zip -========== -``` -package main - -import ( - "bytes" - "log" - "os" - "github.com/alexmullins/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`) - if err != nil { - log.Fatal(err) - } - _, err = io.Copy(w, bytes.NewReader(contents)) - if err != nil { - log.Fatal(err) - } - zipw.Flush() -} -``` - -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 index 3f86df1..df23856 100644 --- a/crypto.go +++ b/crypto.go @@ -22,10 +22,10 @@ import ( type EncryptionMethod int const ( - ZipStandardEncryption EncryptionMethod = 1 - AES128Encryption EncryptionMethod = 2 - AES192Encryption EncryptionMethod = 3 - AES256Encryption EncryptionMethod = 4 + StandardEncryption EncryptionMethod = 1 + AES128Encryption EncryptionMethod = 2 + AES192Encryption EncryptionMethod = 3 + AES256Encryption EncryptionMethod = 4 // AES key lengths aes128 = 16 diff --git a/crypto_test.go b/crypto_test.go index 7ff8ed8..cc5e2de 100644 --- a/crypto_test.go +++ b/crypto_test.go @@ -175,7 +175,7 @@ func TestPasswordWriteSimple(t *testing.T) { contents := []byte("Hello World") conLen := len(contents) - for _, enc := range []EncryptionMethod{ZipStandardEncryption, AES128Encryption, AES192Encryption, AES256Encryption} { + for _, enc := range []EncryptionMethod{StandardEncryption, AES128Encryption, AES192Encryption, AES256Encryption} { raw := new(bytes.Buffer) zipw := NewWriter(raw) w, err := zipw.Encrypt("hello.txt", "golang", enc) @@ -223,7 +223,7 @@ func TestZipCrypto(t *testing.T) { raw := new(bytes.Buffer) zipw := NewWriter(raw) - w, err := zipw.Encrypt("hello.txt", "golang", ZipStandardEncryption) + w, err := zipw.Encrypt("hello.txt", "golang", StandardEncryption) if err != nil { t.Errorf("Expected to create a new FileHeader") } diff --git a/writer.go b/writer.go index 497f407..705215b 100644 --- a/writer.go +++ b/writer.go @@ -229,7 +229,7 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { // check for password var sw io.Writer = fw.compCount if fh.password != nil { - if fh.encryption == ZipStandardEncryption { + if fh.encryption == StandardEncryption { ew, err := ZipCryptoEncryptor(sw, fh.password, fw) if err != nil { return nil, err @@ -321,7 +321,7 @@ func (w *fileWriter) close() error { return err } // if encrypted grab the hmac and write it out - if w.header.IsEncrypted() && w.header.encryption != ZipStandardEncryption { + if w.header.IsEncrypted() && w.header.encryption != StandardEncryption { authCode := w.hmac.Sum(nil) authCode = authCode[:10] _, err := w.compCount.Write(authCode) @@ -332,7 +332,7 @@ func (w *fileWriter) close() error { // update FileHeader fh := w.header.FileHeader // ae-2 we don't write out CRC - if !fh.IsEncrypted() || fh.encryption == ZipStandardEncryption { + if !fh.IsEncrypted() || fh.encryption == StandardEncryption { fh.CRC32 = w.crc32.Sum32() } fh.CompressedSize64 = uint64(w.compCount.count)