57 lines
1.2 KiB
Go
57 lines
1.2 KiB
Go
|
|
package symm
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/cipher"
|
||
|
|
"errors"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
ErrInvalidCTROffset = errors.New("ctr offset must be non-negative")
|
||
|
|
ErrCTRCounterOverflow = errors.New("ctr counter overflow")
|
||
|
|
)
|
||
|
|
|
||
|
|
func xorCTRAtOffset(block cipher.Block, data, iv []byte, offset int64) ([]byte, error) {
|
||
|
|
if offset < 0 {
|
||
|
|
return nil, ErrInvalidCTROffset
|
||
|
|
}
|
||
|
|
if len(iv) != block.BlockSize() {
|
||
|
|
return nil, errors.New("iv length must match block size")
|
||
|
|
}
|
||
|
|
|
||
|
|
counter := make([]byte, len(iv))
|
||
|
|
copy(counter, iv)
|
||
|
|
|
||
|
|
blockSize := int64(block.BlockSize())
|
||
|
|
blockOffset := uint64(offset / blockSize)
|
||
|
|
byteOffset := int(offset % blockSize)
|
||
|
|
if err := addUint64ToCounter(counter, blockOffset); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
stream := cipher.NewCTR(block, counter)
|
||
|
|
if byteOffset > 0 {
|
||
|
|
skip := make([]byte, byteOffset)
|
||
|
|
stream.XORKeyStream(skip, skip)
|
||
|
|
}
|
||
|
|
|
||
|
|
out := make([]byte, len(data))
|
||
|
|
stream.XORKeyStream(out, data)
|
||
|
|
return out, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func addUint64ToCounter(counter []byte, inc uint64) error {
|
||
|
|
if inc == 0 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
for i := len(counter) - 1; i >= 0 && inc > 0; i-- {
|
||
|
|
sum := uint64(counter[i]) + (inc & 0xff)
|
||
|
|
counter[i] = byte(sum)
|
||
|
|
inc = (inc >> 8) + (sum >> 8)
|
||
|
|
}
|
||
|
|
if inc > 0 {
|
||
|
|
return ErrCTRCounterOverflow
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|