// 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" ) type EncryptionMethod int const ( StandardEncryption EncryptionMethod = 1 AES128Encryption EncryptionMethod = 2 AES192Encryption EncryptionMethod = 3 AES256Encryption EncryptionMethod = 4 // 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, 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[:], keysize) 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(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 } // 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, enc EncryptionMethod) (io.Writer, error) { fh := &FileHeader{ Name: name, Method: Deflate, } fh.SetPassword(password) fh.setEncryptionMethod(enc) return w.CreateHeader(fh) }