Compare commits

...

13 Commits

Author SHA1 Message Date
re 5be397991f fix repos 2022-12-13 19:19:31 +03:00
Yakub Kristianto d046722c6f
Create LICENSE 2018-09-14 19:55:37 +07:00
Yakub Kristianto 77ea4e565c
Merge pull request #3 from roberthdevries/master
Fix typo in import statement
2018-06-25 12:16:09 +07:00
Robert de Vries b5e29b11a1
Fix typo in import statement 2018-03-25 12:40:52 +02:00
Yakub Kristianto a58f9a5124
Merge pull request #2 from roberthdevries/master
Add missing "io" import to README example
2018-03-21 20:34:41 +07:00
Robert de Vries 736667b903 Add missing "io" import to README example 2018-03-18 22:44:39 +01:00
yeka 6c433e67d3 Change README to include example 2018-02-23 12:34:12 +07:00
yeka d851e4060f Add encryption method in test 2018-02-23 12:14:14 +07:00
yeka f2052480f0 Write CRC for Standard Zip Encryption 2018-02-23 12:14:14 +07:00
yeka 673b7b98d0 Add encrypt & decrypt using zip standard encryption 2018-02-23 12:14:14 +07:00
yeka feedcee201 Selectable Encryption Method 2018-02-23 12:14:14 +07:00
Alex Mullins f15f84f02c
Merge pull request #1 from farmerx/patch-1
modify readme.md add example
2017-11-16 23:59:17 -06:00
Farmerx b2c4533ffb
完善一下readme.md
Hi! alexmullins:
     在我使用你这个包的时候,着了所有的例子,都没有成功。 最后当我看你你的这个zip fork golang zip 。我恍然大悟,原来你的这个包是这么用的。为了以后更多的人使用你的这个zip包,请接收我的提交
2017-11-16 20:38:45 -06:00
10 changed files with 330 additions and 121 deletions

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (C) 2015 Alex Mullins
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

92
README.md Normal file
View File

@ -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"
"git.internal/re/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"
"git.internal/re/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))
}
}
```

View File

@ -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

View File

@ -19,7 +19,14 @@ import (
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
) )
type EncryptionMethod int
const ( const (
StandardEncryption EncryptionMethod = 1
AES128Encryption EncryptionMethod = 2
AES192Encryption EncryptionMethod = 3
AES256Encryption EncryptionMethod = 4
// AES key lengths // AES key lengths
aes128 = 16 aes128 = 16
aes192 = 24 aes192 = 24
@ -379,13 +386,14 @@ func encryptStream(key []byte, w io.Writer) (io.Writer, error) {
// newEncryptionWriter returns an io.Writer that when written to, 1. writes // newEncryptionWriter returns an io.Writer that when written to, 1. writes
// out the salt, 2. writes out pwv, and 3. writes out authenticated, encrypted // out the salt, 2. writes out pwv, and 3. writes out authenticated, encrypted
// data. The authcode will be written out in fileWriter.close(). // data. The authcode will be written out in fileWriter.close().
func newEncryptionWriter(w io.Writer, password passwordFn, fw *fileWriter) (io.Writer, error) { func newEncryptionWriter(w io.Writer, password passwordFn, fw *fileWriter, aesstrength byte) (io.Writer, error) {
var salt [16]byte keysize := aesKeyLen(aesstrength)
salt := make([]byte, keysize / 2)
_, err := rand.Read(salt[:]) _, err := rand.Read(salt[:])
if err != nil { if err != nil {
return nil, errors.New("zip: unable to generate random salt") 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) fw.hmac = hmac.New(sha1.New, akey)
aw := &authWriter{ aw := &authWriter{
hmac: fw.hmac, hmac: fw.hmac,
@ -424,11 +432,23 @@ func (h *FileHeader) writeWinZipExtra() {
eb.uint16(7) // following data size is 7 eb.uint16(7) // following data size is 7
eb.uint16(2) // ae 2 eb.uint16(2) // ae 2
eb.uint16(0x4541) // "AE" eb.uint16(0x4541) // "AE"
eb.uint8(3) // aes256 eb.uint8(h.aesStrength) // aes256
eb.uint16(h.Method) // original compression method eb.uint16(h.Method) // original compression method
h.Extra = append(h.Extra, buf[:]...) 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() { func (h *FileHeader) setEncryptionBit() {
h.Flags |= 0x1 h.Flags |= 0x1
} }
@ -452,11 +472,12 @@ type passwordFn func() []byte
// contents will be encrypted with AES-256 using the given password. The // 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 // file's contents must be written to the io.Writer before the next call
// to Create, CreateHeader, or Close. // 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{ fh := &FileHeader{
Name: name, Name: name,
Method: Deflate, Method: Deflate,
} }
fh.SetPassword(password) fh.SetPassword(password)
fh.setEncryptionMethod(enc)
return w.CreateHeader(fh) return w.CreateHeader(fh)
} }

View File

@ -175,9 +175,10 @@ func TestPasswordWriteSimple(t *testing.T) {
contents := []byte("Hello World") contents := []byte("Hello World")
conLen := len(contents) conLen := len(contents)
for _, enc := range []EncryptionMethod{StandardEncryption, AES128Encryption, AES192Encryption, AES256Encryption} {
raw := new(bytes.Buffer) raw := new(bytes.Buffer)
zipw := NewWriter(raw) zipw := NewWriter(raw)
w, err := zipw.Encrypt("hello.txt", "golang") w, err := zipw.Encrypt("hello.txt", "golang", enc)
if err != nil { if err != nil {
t.Errorf("Expected to create a new FileHeader") t.Errorf("Expected to create a new FileHeader")
} }
@ -213,4 +214,33 @@ func TestPasswordWriteSimple(t *testing.T) {
if !bytes.Equal(contents, buf.Bytes()) { if !bytes.Equal(contents, buf.Bytes()) {
t.Errorf("Expected the unzipped contents to equal '%s', but was '%s' instead", 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", StandardEncryption)
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()
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())
}
} }

View File

@ -11,7 +11,7 @@ import (
"log" "log"
"os" "os"
"github.com/alexmullins/zip" "git.internal/re/zip"
) )
func ExampleWriter() { func ExampleWriter() {
@ -22,7 +22,7 @@ func ExampleWriter() {
w := zip.NewWriter(buf) w := zip.NewWriter(buf)
// Add some files to the archive. // Add some files to the archive.
var files = []struct { files := []struct {
Name, Body string Name, Body string
}{ }{
{"readme.txt", "This archive contains some text files."}, {"readme.txt", "This archive contains some text files."},
@ -81,7 +81,7 @@ func ExampleWriter_Encrypt() {
// write a password zip // write a password zip
raw := new(bytes.Buffer) raw := new(bytes.Buffer)
zipw := zip.NewWriter(raw) zipw := zip.NewWriter(raw)
w, err := zipw.Encrypt("hello.txt", "golang") w, err := zipw.Encrypt("hello.txt", "golang", zip.AES256Encryption)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -145,7 +145,12 @@ func (f *File) Open() (rc io.ReadCloser, err error) {
rr := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size) rr := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size)
// check for encryption // check for encryption
if f.IsEncrypted() { 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 return
} }
} else { } else {

View File

@ -101,6 +101,7 @@ type FileHeader struct {
// https://www.imperialviolet.org/2015/05/16/aeads.html // https://www.imperialviolet.org/2015/05/16/aeads.html
DeferAuth bool DeferAuth bool
encryption EncryptionMethod
password passwordFn // Returns the password to use when reading/writing password passwordFn // Returns the password to use when reading/writing
ae uint16 ae uint16
aesStrength byte aesStrength byte

View File

@ -229,14 +229,22 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) {
// check for password // check for password
var sw io.Writer = fw.compCount var sw io.Writer = fw.compCount
if fh.password != nil { if fh.password != nil {
// we have a password and need to encrypt. if fh.encryption == StandardEncryption {
fh.writeWinZipExtra() ew, err := ZipCryptoEncryptor(sw, fh.password, fw)
fh.Method = 99 // ok to change, we've gotten the comp and wrote extra
ew, err := newEncryptionWriter(sw, fh.password, fw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sw = ew 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 {
return nil, err
}
sw = ew
}
} }
var err error var err error
fw.comp, err = comp(sw) fw.comp, err = comp(sw)
@ -313,7 +321,7 @@ func (w *fileWriter) close() error {
return err return err
} }
// if encrypted grab the hmac and write it out // 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 := w.hmac.Sum(nil)
authCode = authCode[:10] authCode = authCode[:10]
_, err := w.compCount.Write(authCode) _, err := w.compCount.Write(authCode)
@ -324,7 +332,7 @@ func (w *fileWriter) close() error {
// update FileHeader // update FileHeader
fh := w.header.FileHeader fh := w.header.FileHeader
// ae-2 we don't write out CRC // ae-2 we don't write out CRC
if !fh.IsEncrypted() { if !fh.IsEncrypted() || fh.encryption == StandardEncryption {
fh.CRC32 = w.crc32.Sum32() fh.CRC32 = w.crc32.Sum32()
} }
fh.CompressedSize64 = uint64(w.compCount.count) fh.CompressedSize64 = uint64(w.compCount.count)

109
zipcrypto.go Normal file
View File

@ -0,0 +1,109 @@
package zip
import (
"io"
"bytes"
"hash/crc32"
)
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
}