diff --git a/src/crypto/cipher/ctr.go b/src/crypto/cipher/ctr.go index 3ac0ff74d069c1..110c8a847ab363 100644 --- a/src/crypto/cipher/ctr.go +++ b/src/crypto/cipher/ctr.go @@ -19,10 +19,11 @@ import ( ) type ctr struct { - b Block - ctr []byte - out []byte - outUsed int + b Block + ctr []byte + out []byte + outUsed int + offsetPos int } const streamBufferSize = 512 @@ -36,7 +37,12 @@ type ctrAble interface { // NewCTR returns a Stream which encrypts/decrypts using the given Block in // counter mode. The length of iv must be the same as the Block's block size. + func NewCTR(block Block, iv []byte) Stream { + return NewCTRWithOffset(block, iv, 0) +} + +func NewCTRWithOffset(block Block, iv []byte, offsetPos int) Stream { if ctr, ok := block.(ctrAble); ok { return ctr.NewCTR(iv) } @@ -48,13 +54,56 @@ func NewCTR(block Block, iv []byte) Stream { bufSize = block.BlockSize() } return &ctr{ - b: block, - ctr: bytes.Clone(iv), - out: make([]byte, 0, bufSize), - outUsed: 0, + offsetPos: offsetPos, + b: block, + ctr: IncreaseCtr(offsetPos, block.BlockSize(), iv), + out: make([]byte, 0, bufSize), + outUsed: 0, } } +func IncreaseCtr(offsetPos, BlockSize int, iv []byte) []byte { + + iv = bytes.Clone(iv) + + if len(iv) == 0 || offsetPos <= 0 || BlockSize <= 0 { + return iv + } + + + needAdd := offsetPos / BlockSize + index := 0 + + for { + add := needAdd & 0xff + + tmpIv := iv[:len(iv)-index] + for i := len(tmpIv) - 1; i >= 0; i-- { + if i == len(tmpIv)-1 && int(tmpIv[i])+add > 255 { + tmpIv[i] = byte((int(tmpIv[i]) + add) % 256) + add = 1 + continue + } + tmpIv[i] += byte(add) + if tmpIv[i] != 0 { + break + } + } + + index++ + if index >= len(iv) { + break + } + + needAdd >>= 8 + if needAdd <= 0 { + break + } + } + + return iv +} + func (x *ctr) refill() { remain := len(x.out) - x.outUsed copy(x.out, x.out[x.outUsed:]) @@ -74,6 +123,12 @@ func (x *ctr) refill() { } x.out = x.out[:remain] x.outUsed = 0 + + if x.offsetPos > 0 { + offset := x.offsetPos % x.b.BlockSize() + x.out = x.out[offset:] + x.offsetPos = 0 + } } func (x *ctr) XORKeyStream(dst, src []byte) { diff --git a/src/crypto/cipher/ctr_aes_test.go b/src/crypto/cipher/ctr_aes_test.go index d019ae0d022eb8..9f13f8d2422f6d 100644 --- a/src/crypto/cipher/ctr_aes_test.go +++ b/src/crypto/cipher/ctr_aes_test.go @@ -100,3 +100,40 @@ func TestCTR_AES(t *testing.T) { } } } + +func TestCTRWithOffset_AES(t *testing.T) { + for _, tt := range ctrAESTests { + test := tt.name + + c, err := aes.NewCipher(tt.key) + if err != nil { + t.Errorf("%s: NewCipher(%d bytes) = %s", test, len(tt.key), err) + continue + } + + for j := 0; j <= 5; j += 5 { + in := tt.in[j : len(tt.in)-j] + ctr := cipher.NewCTRWithOffset(c, tt.iv, j) + encrypted := make([]byte, len(in)) + ctr.XORKeyStream(encrypted, in) + if out := tt.out[j : len(in)+j]; !bytes.Equal(out, encrypted) { + t.Errorf("%s/%d: CTR\ninpt %x\nhave %x\nwant %x", test, len(in), in, encrypted, out) + } + } + + for j := 0; j <= 7; j += 7 { + in := tt.out[j : len(tt.out)-j] + ctr := cipher.NewCTRWithOffset(c, tt.iv, j) + plain := make([]byte, len(in)) + ctr.XORKeyStream(plain, in) + if out := tt.in[j : len(in)+j]; !bytes.Equal(out, plain) { + t.Errorf("%s/%d: CTRReader\nhave %x\nwant %x", test, len(out), plain, out) + } + } + + if t.Failed() { + break + } + } + +} diff --git a/src/crypto/cipher/ctr_test.go b/src/crypto/cipher/ctr_test.go index e5cce576c79e26..12052db62cac84 100644 --- a/src/crypto/cipher/ctr_test.go +++ b/src/crypto/cipher/ctr_test.go @@ -53,3 +53,49 @@ func TestCTR(t *testing.T) { } } } + +var ctrAESIncreaseTests = []struct { + iv []byte + offset int + ivWant []byte +}{ + { + []byte{0, 0}, + (100 * 256 * 256 * 256) * 16, + []byte{0, 0}, + }, + { + []byte{0, 0, 0, 0, 100}, + 156 * 16, + []byte{0, 0, 0, 1, 0}, + }, + { + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + 100 * 16, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + { + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + (100*256 + 100) * 16, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 100}, + }, + { + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + (100 * 256 * 256 * 256) * 16, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0}, + }, + { + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + (100 * 256 * 256 * 256) * 16, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0}, + }, +} + +func TestIncreaseCtr(t *testing.T) { + for _, item := range ctrAESIncreaseTests { + ivNext := cipher.IncreaseCtr(item.offset, 16, item.iv) + if !bytes.Equal(ivNext, item.ivWant) { + t.Errorf("for iv %d\noffset %x\nwant %x", item.iv, item.offset, item.ivWant) + } + } +}