diff --git a/README.txt b/README.txt index 116df0b..02847b5 100644 --- a/README.txt +++ b/README.txt @@ -11,10 +11,7 @@ hello.txt -> compress -> encrypt -> .zip -> decrypt -> decompress -> hello.txt Roadmap ============================================================================== -Reading - Working on it. Seems like the AES-CTR counter incrementer for WinZip -encryption follows a little-endian, left-aligned counter whereas Go's AES-CTR -implementation is big-endian, right-aligned counter. Will have to read block by -block and increment the counter by hand. +Reading - Works. See ctr.go for implementation. Writing - Not started. Testing - Needs more. diff --git a/ctr.go b/ctr.go new file mode 100644 index 0000000..03c9ba5 --- /dev/null +++ b/ctr.go @@ -0,0 +1,104 @@ +// 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. + +// 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 reimplementation of Go's CTR mode to allow +// for little-endian, left-aligned uint32 counter. Go's +// NewCTR follows the NIST Standard SP 800-38A, pp 13-15 +// which has a big-endian, right-aligned counter. WinZip +// AES requires the CTR mode to have a little-endian, +// left-aligned counter. + +package zip + +import "crypto/cipher" + +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. +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 +} diff --git a/reader.go b/reader.go index 2ce8cfd..5186d38 100644 --- a/reader.go +++ b/reader.go @@ -231,19 +231,16 @@ func newDecryptionReader(r io.Reader, f *File) (io.Reader, error) { if !checkAuthentication(data, authcode, authKey) { return nil, ErrDecryption } - // set the IV - // see: https://forum.golangbridge.org/t/iv-counter-help-for-aes-ctr/1369 - var iv [aes.BlockSize]byte - iv[0] = 1 - return decryptStream(data, decKey, iv[:]), nil + + return decryptStream(data, decKey), nil } -func decryptStream(ciphertext, key, iv []byte) io.Reader { +func decryptStream(ciphertext, key []byte) io.Reader { block, err := aes.NewCipher(key) if err != nil { return nil } - stream := cipher.NewCTR(block, iv) + stream := NewWinZipCTR(block) // Not decrypting stream correctly if the number of bytes being read is >16 reader := cipher.StreamReader{S: stream, R: bytes.NewReader(ciphertext)} return reader