xs/vendor/blitter.com/go/chacha20/chacha20.go
2020-01-30 12:18:39 -08:00

290 lines
7.4 KiB
Go

// Copryright (C) 2019 Yawning Angel
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package chacha20 implements the ChaCha20 stream cipher.
package chacha20 // import "blitter.com/go/chacha20"
import (
"crypto/cipher"
"encoding/binary"
"errors"
"math"
"blitter.com/go/chacha20/internal/api"
"blitter.com/go/chacha20/internal/hardware"
"blitter.com/go/chacha20/internal/ref"
)
const (
// KeySize is the ChaCha20 key size in bytes.
KeySize = 32
// NonceSize is the ChaCha20 nonce size in bytes.
NonceSize = 8
// INonceSize is the IETF ChaCha20 nonce size in bytes.
INonceSize = 12
// XNonceSize is the XChaCha20 nonce size in bytes.
XNonceSize = 24
// HNonceSize is the HChaCha20 nonce size in bytes.
HNonceSize = 16
)
var (
// ErrInvalidKey is the error returned when the key is invalid.
ErrInvalidKey = errors.New("chacha20: key length must be KeySize bytes")
// ErrInvalidNonce is the error returned when the nonce is invalid.
ErrInvalidNonce = errors.New("chacha20: nonce length must be NonceSize/INonceSize/XNonceSize bytes")
// ErrInvalidCounter is the error returned when the counter is invalid.
ErrInvalidCounter = errors.New("chacha20: block counter is invalid (out of range)")
supportedImpls []api.Implementation
activeImpl api.Implementation
_ cipher.Stream = (*Cipher)(nil)
)
// Cipher is an instance of ChaCha20/XChaCha20 using a particular key and nonce.
type Cipher struct {
state [api.StateSize]uint32
buf [api.BlockSize]byte
off int
ietf bool
}
// Reset zeros the key data so that it will no longer appear in the process's
// memory.
func (c *Cipher) Reset() {
for i := range c.state {
c.state[i] = 0
}
for i := range c.buf {
c.buf[i] = 0
}
}
// Seek sets the block counter to a given offset.
func (c *Cipher) Seek(blockCounter uint64) error {
if c.ietf {
if blockCounter > math.MaxUint32 {
return ErrInvalidCounter
}
c.state[12] = uint32(blockCounter)
} else {
c.state[12] = uint32(blockCounter)
c.state[13] = uint32(blockCounter >> 32)
}
c.off = api.BlockSize
return nil
}
// ReKey reinitializes the ChaCha20/XChaCha20 instance with the provided key
// and nonce.
func (c *Cipher) ReKey(key, nonce []byte) error {
c.Reset()
return c.doReKey(key, nonce)
}
func (c *Cipher) doReKey(key, nonce []byte) error {
if len(key) != KeySize {
return ErrInvalidKey
}
var subKey []byte
switch len(nonce) {
case NonceSize, INonceSize:
case XNonceSize:
subKey = c.buf[:KeySize]
activeImpl.HChaCha(key, nonce, subKey)
key = subKey
nonce = nonce[16:24]
default:
return ErrInvalidNonce
}
_ = key[31] // Force bounds check elimination.
c.state[0] = api.Sigma0
c.state[1] = api.Sigma1
c.state[2] = api.Sigma2
c.state[3] = api.Sigma3
c.state[4] = binary.LittleEndian.Uint32(key[0:4])
c.state[5] = binary.LittleEndian.Uint32(key[4:8])
c.state[6] = binary.LittleEndian.Uint32(key[8:12])
c.state[7] = binary.LittleEndian.Uint32(key[12:16])
c.state[8] = binary.LittleEndian.Uint32(key[16:20])
c.state[9] = binary.LittleEndian.Uint32(key[20:24])
c.state[10] = binary.LittleEndian.Uint32(key[24:28])
c.state[11] = binary.LittleEndian.Uint32(key[28:32])
c.state[12] = 0
if len(nonce) == INonceSize {
_ = nonce[11] // Force bounds check elimination.
c.state[13] = binary.LittleEndian.Uint32(nonce[0:4])
c.state[14] = binary.LittleEndian.Uint32(nonce[4:8])
c.state[15] = binary.LittleEndian.Uint32(nonce[8:12])
c.ietf = true
} else {
_ = nonce[7] // Force bounds check elimination.
c.state[13] = 0
c.state[14] = binary.LittleEndian.Uint32(nonce[0:4])
c.state[15] = binary.LittleEndian.Uint32(nonce[4:8])
c.ietf = false
}
c.off = api.BlockSize
if subKey != nil {
for i := range subKey {
subKey[i] = 0
}
}
return nil
}
// New returns a new ChaCha20/XChaCha20 instance.
func New(key, nonce []byte) (*Cipher, error) {
var c Cipher
if err := c.doReKey(key, nonce); err != nil {
return nil, err
}
return &c, nil
}
// HChaCha is the HChaCha20 hash function used to make XChaCha.
func HChaCha(key, nonce []byte, dst *[32]byte) {
activeImpl.HChaCha(key, nonce, dst[:])
}
// XORKeyStream sets dst to the result of XORing src with the key stream. Dst
// and src may be the same slice but otherwise should not overlap.
func (c *Cipher) XORKeyStream(dst, src []byte) {
if len(dst) < len(src) {
src = src[:len(dst)]
}
for remaining := len(src); remaining > 0; {
// Process multiple blocks at once.
if c.off == api.BlockSize {
nrBlocks := remaining / api.BlockSize
directBytes := nrBlocks * api.BlockSize
if nrBlocks > 0 {
c.doBlocks(dst, src, nrBlocks)
remaining -= directBytes
if remaining == 0 {
return
}
dst = dst[directBytes:]
src = src[directBytes:]
}
// If there's a partial block, generate 1 block of keystream into
// the internal buffer.
c.doBlocks(c.buf[:], nil, 1)
c.off = 0
}
// Process partial blocks from the buffered keystream.
toXor := api.BlockSize - c.off
if remaining < toXor {
toXor = remaining
}
if toXor > 0 {
// The inliner doesn't want to inline this function, but my
// attempts to force BCE don't seem to work with manual
// inlining.
//
// Taking the extra function call overhead here appears to be
// worth it.
c.xorBufBytes(dst, src, toXor)
dst = dst[toXor:]
src = src[toXor:]
remaining -= toXor
}
}
}
func (c *Cipher) xorBufBytes(dst, src []byte, n int) {
// Force bounds check elimination.
buf := c.buf[c.off:]
_ = buf[n-1]
_ = dst[n-1]
_ = src[n-1]
for i := 0; i < n; i++ {
dst[i] = buf[i] ^ src[i]
}
c.off += n
}
// KeyStream sets dst to the raw keystream.
func (c *Cipher) KeyStream(dst []byte) {
for remaining := len(dst); remaining > 0; {
// Process multiple blocks at once.
if c.off == api.BlockSize {
nrBlocks := remaining / api.BlockSize
directBytes := nrBlocks * api.BlockSize
if nrBlocks > 0 {
c.doBlocks(dst, nil, nrBlocks)
remaining -= directBytes
if remaining == 0 {
return
}
dst = dst[directBytes:]
}
// If there's a partial block, generate 1 block of keystream into
// the internal buffer.
c.doBlocks(c.buf[:], nil, 1)
c.off = 0
}
// Process partial blocks from the buffered keystream.
toCopy := api.BlockSize - c.off
if remaining < toCopy {
toCopy = remaining
}
if toCopy > 0 {
copy(dst[:toCopy], c.buf[c.off:c.off+toCopy])
dst = dst[toCopy:]
remaining -= toCopy
c.off += toCopy
}
}
}
func (c *Cipher) doBlocks(dst, src []byte, nrBlocks int) {
if c.ietf {
ctr := uint64(c.state[12])
if ctr+uint64(nrBlocks) > math.MaxUint32 {
panic("chacha20: will exceed key stream per nonce limit")
}
}
activeImpl.Blocks(&c.state, dst, src, nrBlocks)
}
func init() {
supportedImpls = hardware.Register(supportedImpls)
supportedImpls = ref.Register(supportedImpls)
activeImpl = supportedImpls[0]
}