mirror of
https://gogs.blitter.com/RLabs/xs
synced 2024-08-14 10:26:42 +00:00
291 lines
7.4 KiB
Go
291 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]
|
|
}
|