463 lines
11 KiB
Go
463 lines
11 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package zip
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"crypto/subtle"
|
|
"errors"
|
|
"hash"
|
|
"io"
|
|
|
|
"golang.org/x/crypto/pbkdf2"
|
|
)
|
|
|
|
const (
|
|
// AES key lengths
|
|
aes128 = 16
|
|
aes192 = 24
|
|
aes256 = 32
|
|
)
|
|
|
|
func aesKeyLen(strength byte) int {
|
|
switch strength {
|
|
case 1:
|
|
return aes128
|
|
case 2:
|
|
return aes192
|
|
case 3:
|
|
return aes256
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// Encryption/Decryption Errors
|
|
var (
|
|
ErrDecryption = errors.New("zip: decryption error")
|
|
ErrPassword = errors.New("zip: invalid password")
|
|
ErrAuthentication = errors.New("zip: authentication failed")
|
|
)
|
|
|
|
// Counter (CTR) mode.
|
|
// CTR converts a block cipher into a stream cipher by
|
|
// repeatedly encrypting an incrementing counter and
|
|
// xoring the resulting stream of data with the input.
|
|
|
|
// This is a re-implementation of Go's CTR mode to allow
|
|
// for a little-endian, left-aligned uint32 counter, which
|
|
// is required for WinZip AES encryption. Go's cipher.NewCTR
|
|
// follows the NIST Standard SP 800-38A, pp 13-15
|
|
// which has a big-endian, right-aligned counter.
|
|
|
|
type ctr struct {
|
|
b cipher.Block
|
|
ctr []byte
|
|
out []byte
|
|
outUsed int
|
|
}
|
|
|
|
const streamBufferSize = 512
|
|
|
|
// NewWinZipCTR returns a Stream which encrypts/decrypts using the given Block in
|
|
// counter mode. The counter is initially set to 1 per WinZip AES.
|
|
func newWinZipCTR(block cipher.Block) cipher.Stream {
|
|
bufSize := streamBufferSize
|
|
if bufSize < block.BlockSize() {
|
|
bufSize = block.BlockSize()
|
|
}
|
|
// Set the IV (counter) to 1
|
|
iv := make([]byte, block.BlockSize())
|
|
iv[0] = 1
|
|
return &ctr{
|
|
b: block,
|
|
ctr: iv,
|
|
out: make([]byte, 0, bufSize),
|
|
outUsed: 0,
|
|
}
|
|
}
|
|
|
|
func (x *ctr) refill() {
|
|
remain := len(x.out) - x.outUsed
|
|
if remain > x.outUsed {
|
|
return
|
|
}
|
|
copy(x.out, x.out[x.outUsed:])
|
|
x.out = x.out[:cap(x.out)]
|
|
bs := x.b.BlockSize()
|
|
for remain < len(x.out)-bs {
|
|
x.b.Encrypt(x.out[remain:], x.ctr)
|
|
remain += bs
|
|
|
|
// Increment counter
|
|
// for i := len(x.ctr) - 1; i >= 0; i-- {
|
|
// x.ctr[i]++
|
|
// if x.ctr[i] != 0 {
|
|
// break
|
|
// }
|
|
// }
|
|
|
|
// Change to allow for little-endian,
|
|
// left-aligned counter
|
|
for i := 0; i < len(x.ctr); i++ {
|
|
x.ctr[i]++
|
|
if x.ctr[i] != 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
x.out = x.out[:remain]
|
|
x.outUsed = 0
|
|
}
|
|
|
|
func (x *ctr) XORKeyStream(dst, src []byte) {
|
|
for len(src) > 0 {
|
|
if x.outUsed >= len(x.out)-x.b.BlockSize() {
|
|
x.refill()
|
|
}
|
|
n := xorBytes(dst, src, x.out[x.outUsed:])
|
|
dst = dst[n:]
|
|
src = src[n:]
|
|
x.outUsed += n
|
|
}
|
|
}
|
|
|
|
func xorBytes(dst, a, b []byte) int {
|
|
n := len(a)
|
|
if len(b) < n {
|
|
n = len(b)
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
dst[i] = a[i] ^ b[i]
|
|
}
|
|
return n
|
|
}
|
|
|
|
// newAuthReader returns either a buffered or streaming authentication reader.
|
|
// Buffered authentication is recommended. Streaming authentication is only
|
|
// recommended if: 1. you buffer the data yourself and wait for authentication
|
|
// before streaming to another source such as the network, or 2. you just don't
|
|
// care about authenticating unknown ciphertext before use :).
|
|
func newAuthReader(akey []byte, data, adata io.Reader, streaming bool) io.Reader {
|
|
ar := authReader{
|
|
data: data,
|
|
adata: adata,
|
|
mac: hmac.New(sha1.New, akey),
|
|
err: nil,
|
|
auth: false,
|
|
}
|
|
if streaming {
|
|
return &ar
|
|
}
|
|
return &bufferedAuthReader{
|
|
ar,
|
|
new(bytes.Buffer),
|
|
}
|
|
}
|
|
|
|
// Streaming authentication
|
|
type authReader struct {
|
|
data io.Reader // data to be authenticated
|
|
adata io.Reader // the authentication code to read
|
|
mac hash.Hash // hmac hash
|
|
err error
|
|
auth bool
|
|
}
|
|
|
|
func (a *authReader) Read(p []byte) (int, error) {
|
|
if a.err != nil {
|
|
return 0, a.err
|
|
}
|
|
end := false
|
|
// read underlying data
|
|
n, err := a.data.Read(p)
|
|
if err != nil && err != io.EOF {
|
|
a.err = err
|
|
return n, a.err
|
|
} else if err == io.EOF {
|
|
// if we are at the end, calculate the mac
|
|
end = true
|
|
a.err = err
|
|
}
|
|
// write any data to mac
|
|
_, err = a.mac.Write(p[:n])
|
|
if err != nil {
|
|
a.err = err
|
|
return n, a.err
|
|
}
|
|
if end {
|
|
ab := new(bytes.Buffer)
|
|
_, err = io.Copy(ab, a.adata)
|
|
if err != nil || ab.Len() != 10 {
|
|
a.err = ErrDecryption
|
|
return n, a.err
|
|
}
|
|
if !a.checkAuthentication(ab.Bytes()) {
|
|
a.err = ErrAuthentication
|
|
return n, a.err
|
|
}
|
|
}
|
|
return n, a.err
|
|
}
|
|
|
|
// buffered authentication
|
|
type bufferedAuthReader struct {
|
|
authReader
|
|
buf *bytes.Buffer // buffer to store data to authenticate
|
|
}
|
|
|
|
func (a *bufferedAuthReader) Read(b []byte) (int, error) {
|
|
// check for sticky error
|
|
if a.err != nil {
|
|
return 0, a.err
|
|
}
|
|
// make sure we have auth'ed before we send any data
|
|
if !a.auth {
|
|
_, err := io.Copy(a.buf, a.data)
|
|
if err != nil {
|
|
a.err = err
|
|
return 0, a.err
|
|
}
|
|
ab := new(bytes.Buffer)
|
|
nn, err := io.Copy(ab, a.adata)
|
|
if err != nil {
|
|
a.err = err
|
|
return 0, a.err
|
|
} else if nn != 10 {
|
|
a.err = ErrDecryption
|
|
return 0, a.err
|
|
}
|
|
_, err = a.mac.Write(a.buf.Bytes())
|
|
if err != nil {
|
|
a.err = err
|
|
return 0, a.err
|
|
}
|
|
if !a.checkAuthentication(ab.Bytes()) {
|
|
a.err = ErrAuthentication
|
|
return 0, a.err
|
|
}
|
|
}
|
|
// so we've authenticated the data, now just pass it on.
|
|
n, err := a.buf.Read(b)
|
|
if err != nil {
|
|
a.err = err
|
|
}
|
|
return n, a.err
|
|
}
|
|
|
|
func (a *authReader) checkAuthentication(authcode []byte) bool {
|
|
expectedAuthCode := a.mac.Sum(nil)
|
|
// Truncate at the first 10 bytes
|
|
expectedAuthCode = expectedAuthCode[:10]
|
|
a.auth = subtle.ConstantTimeCompare(expectedAuthCode, authcode) > 0
|
|
return a.auth
|
|
}
|
|
|
|
func checkPasswordVerification(pwvv, pwv []byte) bool {
|
|
b := subtle.ConstantTimeCompare(pwvv, pwv) > 0
|
|
return b
|
|
}
|
|
|
|
func generateKeys(password, salt []byte, keySize int) (encKey, authKey, pwv []byte) {
|
|
totalSize := (keySize * 2) + 2 // enc + auth + pv sizes
|
|
key := pbkdf2.Key(password, salt, 1000, totalSize, sha1.New)
|
|
encKey = key[:keySize]
|
|
authKey = key[keySize : keySize*2]
|
|
pwv = key[keySize*2:]
|
|
return
|
|
}
|
|
|
|
// newDecryptionReader returns an authenticated, decryption reader
|
|
func newDecryptionReader(r *io.SectionReader, f *File) (io.Reader, error) {
|
|
keyLen := aesKeyLen(f.aesStrength)
|
|
saltLen := keyLen / 2 // salt is half of key len
|
|
if saltLen == 0 {
|
|
return nil, ErrDecryption
|
|
}
|
|
// grab the salt and pwvv
|
|
saltpwvv := make([]byte, saltLen+2)
|
|
if _, err := r.Read(saltpwvv); err != nil {
|
|
return nil, err
|
|
}
|
|
salt := saltpwvv[:saltLen]
|
|
pwvv := saltpwvv[saltLen : saltLen+2]
|
|
// generate keys only if we have a password
|
|
if f.password == nil {
|
|
return nil, ErrPassword
|
|
}
|
|
decKey, authKey, pwv := generateKeys(f.password(), salt, keyLen)
|
|
if !checkPasswordVerification(pwv, pwvv) {
|
|
return nil, ErrPassword
|
|
}
|
|
dataOff := int64(saltLen + 2)
|
|
dataLen := int64(f.CompressedSize64 - uint64(saltLen) - 2 - 10)
|
|
// // TODO(alex): Should the compressed sizes be fixed?
|
|
// // Not the ideal place to do this.
|
|
// f.CompressedSize64 = uint64(dataLen)
|
|
// f.CompressedSize = uint32(dataLen)
|
|
data := io.NewSectionReader(r, dataOff, dataLen)
|
|
authOff := dataOff + dataLen
|
|
authcode := io.NewSectionReader(r, authOff, 10)
|
|
ar := newAuthReader(authKey, data, authcode, f.DeferAuth)
|
|
dr := decryptStream(decKey, ar)
|
|
if dr == nil {
|
|
return nil, ErrDecryption
|
|
}
|
|
return dr, nil
|
|
}
|
|
|
|
func decryptStream(key []byte, ciphertext io.Reader) io.Reader {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
stream := newWinZipCTR(block)
|
|
reader := &cipher.StreamReader{S: stream, R: ciphertext}
|
|
return reader
|
|
}
|
|
|
|
// writes encrypted data to hmac as it passes through
|
|
type authWriter struct {
|
|
hmac hash.Hash // from fw.hmac
|
|
w io.Writer // this will be the compCount writer
|
|
}
|
|
|
|
func (aw *authWriter) Write(p []byte) (int, error) {
|
|
_, err := aw.hmac.Write(p)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return aw.w.Write(p)
|
|
}
|
|
|
|
// writes out the salt, pwv, and then the encrypted file data
|
|
type encryptionWriter struct {
|
|
pwv []byte
|
|
salt []byte
|
|
w io.Writer // where to write the salt + pwv
|
|
es io.Writer // where to write plaintext
|
|
first bool // first write
|
|
err error
|
|
}
|
|
|
|
func (ew *encryptionWriter) Write(p []byte) (int, error) {
|
|
if ew.err != nil {
|
|
return 0, ew.err
|
|
}
|
|
if ew.first {
|
|
// if our first time writing
|
|
// must write out the salt and pwv first unencrypted
|
|
_, err1 := ew.w.Write(ew.salt)
|
|
_, err2 := ew.w.Write(ew.pwv)
|
|
if err1 != nil || err2 != nil {
|
|
ew.err = errors.New("zip: error writing salt or pwv")
|
|
return 0, ew.err
|
|
}
|
|
ew.first = false
|
|
}
|
|
// now just pass on to the encryption stream
|
|
return ew.es.Write(p)
|
|
}
|
|
|
|
func encryptStream(key []byte, w io.Writer) (io.Writer, error) {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stream := newWinZipCTR(block)
|
|
writer := &cipher.StreamWriter{S: stream, W: w}
|
|
return writer, nil
|
|
}
|
|
|
|
// 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
|
|
_, err := rand.Read(salt[:])
|
|
if err != nil {
|
|
return nil, errors.New("zip: unable to generate random salt")
|
|
}
|
|
ekey, akey, pwv := generateKeys(password(), salt[:], aes256)
|
|
fw.hmac = hmac.New(sha1.New, akey)
|
|
aw := &authWriter{
|
|
hmac: fw.hmac,
|
|
w: w,
|
|
}
|
|
es, err := encryptStream(ekey, aw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ew := &encryptionWriter{
|
|
pwv: pwv,
|
|
salt: salt[:],
|
|
w: w,
|
|
es: es,
|
|
first: true,
|
|
}
|
|
return ew, nil
|
|
}
|
|
|
|
// IsEncrypted indicates whether this file's data is encrypted.
|
|
func (h *FileHeader) IsEncrypted() bool {
|
|
return h.Flags&0x1 == 1
|
|
}
|
|
|
|
// WinZip AE-2 specifies that no CRC value is written and
|
|
// should be skipped when reading.
|
|
func (h *FileHeader) isAE2() bool {
|
|
return h.ae == 2
|
|
}
|
|
|
|
func (h *FileHeader) writeWinZipExtra() {
|
|
// total size is 11 bytes
|
|
var buf [11]byte
|
|
eb := writeBuf(buf[:])
|
|
eb.uint16(winzipAesExtraId) // 0x9901
|
|
eb.uint16(7) // following data size is 7
|
|
eb.uint16(2) // ae 2
|
|
eb.uint16(0x4541) // "AE"
|
|
eb.uint8(3) // aes256
|
|
eb.uint16(h.Method) // original compression method
|
|
h.Extra = append(h.Extra, buf[:]...)
|
|
}
|
|
|
|
func (h *FileHeader) setEncryptionBit() {
|
|
h.Flags |= 0x1
|
|
}
|
|
|
|
// SetPassword sets the password used for encryption/decryption.
|
|
func (h *FileHeader) SetPassword(password string) {
|
|
if !h.IsEncrypted() {
|
|
h.setEncryptionBit()
|
|
}
|
|
h.password = func() []byte {
|
|
return []byte(password)
|
|
}
|
|
}
|
|
|
|
// PasswordFn is a function that returns the password
|
|
// as a byte slice
|
|
type passwordFn func() []byte
|
|
|
|
// Encrypt adds a file to the zip file using the provided name.
|
|
// It returns a Writer to which the file contents should be written. File
|
|
// 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) {
|
|
fh := &FileHeader{
|
|
Name: name,
|
|
Method: Deflate,
|
|
}
|
|
fh.SetPassword(password)
|
|
return w.CreateHeader(fh)
|
|
}
|