Compare commits

...

4 Commits

Author SHA1 Message Date
yeka 0c390aad59 Add encryption method in test 2016-10-16 22:06:07 +07:00
yeka 166e1a9aa1 Write CRC for Standard Zip Encryption 2016-10-16 22:02:49 +07:00
yeka 00f9f9dae9 Add encrypt & decrypt using zip standard encryption 2016-10-16 21:28:36 +07:00
yeka b64137d021 Selectable Encryption Method 2016-10-16 20:47:17 +07:00
8 changed files with 220 additions and 43 deletions

View File

@ -1,3 +1,6 @@
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.

View File

@ -19,7 +19,14 @@ import (
"golang.org/x/crypto/pbkdf2"
)
type EncryptionMethod int
const (
ZipStandardEncryption EncryptionMethod = 1
AES128Encryption EncryptionMethod = 2
AES192Encryption EncryptionMethod = 3
AES256Encryption EncryptionMethod = 4
// AES key lengths
aes128 = 16
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
// 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,
@ -424,11 +432,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 +472,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)
}

View File

@ -175,9 +175,55 @@ func TestPasswordWriteSimple(t *testing.T) {
contents := []byte("Hello World")
conLen := len(contents)
for _, enc := range []EncryptionMethod{ZipStandardEncryption, 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", ZipStandardEncryption)
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())
}
}

View File

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

View File

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

View File

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

View File

@ -229,14 +229,22 @@ 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 == ZipStandardEncryption {
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 {
return nil, err
}
sw = ew
}
sw = ew
}
var err error
fw.comp, err = comp(sw)
@ -313,7 +321,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 != ZipStandardEncryption {
authCode := w.hmac.Sum(nil)
authCode = authCode[:10]
_, err := w.compCount.Write(authCode)
@ -324,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() {
if !fh.IsEncrypted() || fh.encryption == ZipStandardEncryption {
fh.CRC32 = w.crc32.Sum32()
}
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
}