Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
50 changes: 49 additions & 1 deletion crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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()
Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
23 changes: 11 additions & 12 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -145,26 +145,25 @@ 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
}
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() {
Expand All @@ -176,7 +175,7 @@ func (f *File) Open() (rc io.ReadCloser, err error) {
f: f,
desr: desr,
}
return
return rc, nil
}

type checksumReader struct {
Expand Down Expand Up @@ -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
Expand Down
Binary file added testdata/password-abc.zip
Binary file not shown.
53 changes: 39 additions & 14 deletions zipcrypto.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
}