forked from mirror/zip
Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
re | 5be397991f | |
Yakub Kristianto | d046722c6f | |
Yakub Kristianto | 77ea4e565c | |
Robert de Vries | b5e29b11a1 | |
Yakub Kristianto | a58f9a5124 | |
Robert de Vries | 736667b903 | |
yeka | 6c433e67d3 | |
yeka | d851e4060f | |
yeka | f2052480f0 | |
yeka | 673b7b98d0 | |
yeka | feedcee201 | |
Alex Mullins | f15f84f02c | |
Farmerx | b2c4533ffb |
|
@ -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.
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
77
README.txt
77
README.txt
|
@ -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
|
|
31
crypto.go
31
crypto.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -214,3 +215,32 @@ func TestPasswordWriteSimple(t *testing.T) {
|
||||||
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
20
writer.go
20
writer.go
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue