gost_software/cmd/gost/vendor/github.com/codahale/chacha20/chacha20.go
2017-01-09 11:48:37 +08:00

236 lines
7.2 KiB
Go

// Package chacha20 provides a pure Go implementation of ChaCha20, a fast,
// secure stream cipher.
//
// From Bernstein, Daniel J. "ChaCha, a variant of Salsa20." Workshop Record of
// SASC. 2008. (http://cr.yp.to/chacha/chacha-20080128.pdf):
//
// ChaCha8 is a 256-bit stream cipher based on the 8-round cipher Salsa20/8.
// The changes from Salsa20/8 to ChaCha8 are designed to improve diffusion per
// round, conjecturally increasing resistance to cryptanalysis, while
// preserving -- and often improving -- time per round. ChaCha12 and ChaCha20
// are analogous modifications of the 12-round and 20-round ciphers Salsa20/12
// and Salsa20/20. This paper presents the ChaCha family and explains the
// differences between Salsa20 and ChaCha.
//
// For more information, see http://cr.yp.to/chacha.html
package chacha20
import (
"crypto/cipher"
"encoding/binary"
"errors"
"unsafe"
)
const (
// KeySize is the length of ChaCha20 keys, in bytes.
KeySize = 32
// NonceSize is the length of ChaCha20 nonces, in bytes.
NonceSize = 8
// XNonceSize is the length of XChaCha20 nonces, in bytes.
XNonceSize = 24
)
var (
// ErrInvalidKey is returned when the provided key is not 256 bits long.
ErrInvalidKey = errors.New("invalid key length (must be 256 bits)")
// ErrInvalidNonce is returned when the provided nonce is not 64 bits long.
ErrInvalidNonce = errors.New("invalid nonce length (must be 64 bits)")
// ErrInvalidXNonce is returned when the provided nonce is not 192 bits
// long.
ErrInvalidXNonce = errors.New("invalid nonce length (must be 192 bits)")
// ErrInvalidRounds is returned when the provided rounds is not
// 8, 12, or 20.
ErrInvalidRounds = errors.New("invalid rounds number (must be 8, 12, or 20)")
)
// New creates and returns a new cipher.Stream. The key argument must be 256
// bits long, and the nonce argument must be 64 bits long. The nonce must be
// randomly generated or used only once. This Stream instance must not be used
// to encrypt more than 2^70 bytes (~1 zettabyte).
func New(key []byte, nonce []byte) (cipher.Stream, error) {
return NewWithRounds(key, nonce, 20)
}
// NewWithRounds creates and returns a new cipher.Stream just like New but
// the rounds number of 8, 12, or 20 can be specified.
func NewWithRounds(key []byte, nonce []byte, rounds uint8) (cipher.Stream, error) {
if len(key) != KeySize {
return nil, ErrInvalidKey
}
if len(nonce) != NonceSize {
return nil, ErrInvalidNonce
}
if (rounds != 8) && (rounds != 12) && (rounds != 20) {
return nil, ErrInvalidRounds
}
s := new(stream)
s.init(key, nonce, rounds)
s.advance()
return s, nil
}
// NewXChaCha creates and returns a new cipher.Stream. The key argument must be
// 256 bits long, and the nonce argument must be 192 bits long. The nonce must
// be randomly generated or only used once. This Stream instance must not be
// used to encrypt more than 2^70 bytes (~1 zetta byte).
func NewXChaCha(key []byte, nonce []byte) (cipher.Stream, error) {
return NewXChaChaWithRounds(key, nonce, 20)
}
// NewXChaChaWithRounds creates and returns a new cipher.Stream just like
// NewXChaCha but the rounds number of 8, 12, or 20 can be specified.
func NewXChaChaWithRounds(key []byte, nonce []byte, rounds uint8) (cipher.Stream, error) {
if len(key) != KeySize {
return nil, ErrInvalidKey
}
if len(nonce) != XNonceSize {
return nil, ErrInvalidXNonce
}
if (rounds != 8) && (rounds != 12) && (rounds != 20) {
return nil, ErrInvalidRounds
}
s := new(stream)
s.init(key, nonce, rounds)
// Call HChaCha to derive the subkey using the key and the first 16 bytes
// of the nonce, and re-initialize the state using the subkey and the
// remaining nonce.
blockArr := (*[stateSize]uint32)(unsafe.Pointer(&s.block))
core(&s.state, blockArr, s.rounds, true)
copy(s.state[4:8], blockArr[0:4])
copy(s.state[8:12], blockArr[12:16])
s.state[12] = 0
s.state[13] = 0
s.state[14] = binary.LittleEndian.Uint32(nonce[16:])
s.state[15] = binary.LittleEndian.Uint32(nonce[20:])
s.advance()
return s, nil
}
type stream struct {
state [stateSize]uint32 // the state as an array of 16 32-bit words
block [blockSize]byte // the keystream as an array of 64 bytes
offset int // the offset of used bytes in block
rounds uint8
}
func (s *stream) XORKeyStream(dst, src []byte) {
// Stride over the input in 64-byte blocks, minus the amount of keystream
// previously used. This will produce best results when processing blocks
// of a size evenly divisible by 64.
i := 0
max := len(src)
for i < max {
gap := blockSize - s.offset
limit := i + gap
if limit > max {
limit = max
}
o := s.offset
for j := i; j < limit; j++ {
dst[j] = src[j] ^ s.block[o]
o++
}
i += gap
s.offset = o
if o == blockSize {
s.advance()
}
}
}
func (s *stream) init(key []byte, nonce []byte, rounds uint8) {
// the magic constants for 256-bit keys
s.state[0] = 0x61707865
s.state[1] = 0x3320646e
s.state[2] = 0x79622d32
s.state[3] = 0x6b206574
s.state[4] = binary.LittleEndian.Uint32(key[0:])
s.state[5] = binary.LittleEndian.Uint32(key[4:])
s.state[6] = binary.LittleEndian.Uint32(key[8:])
s.state[7] = binary.LittleEndian.Uint32(key[12:])
s.state[8] = binary.LittleEndian.Uint32(key[16:])
s.state[9] = binary.LittleEndian.Uint32(key[20:])
s.state[10] = binary.LittleEndian.Uint32(key[24:])
s.state[11] = binary.LittleEndian.Uint32(key[28:])
switch len(nonce) {
case NonceSize:
// ChaCha20 uses 8 byte nonces.
s.state[12] = 0
s.state[13] = 0
s.state[14] = binary.LittleEndian.Uint32(nonce[0:])
s.state[15] = binary.LittleEndian.Uint32(nonce[4:])
case XNonceSize:
// XChaCha20 derives the subkey via HChaCha initialized
// with the first 16 bytes of the nonce.
s.state[12] = binary.LittleEndian.Uint32(nonce[0:])
s.state[13] = binary.LittleEndian.Uint32(nonce[4:])
s.state[14] = binary.LittleEndian.Uint32(nonce[8:])
s.state[15] = binary.LittleEndian.Uint32(nonce[12:])
default:
// Never happens, both ctors validate the nonce length.
panic("invalid nonce size")
}
s.rounds = rounds
}
// BUG(codahale): Totally untested on big-endian CPUs. Would very much
// appreciate someone with an ARM device giving this a swing.
// advances the keystream
func (s *stream) advance() {
core(&s.state, (*[stateSize]uint32)(unsafe.Pointer(&s.block)), s.rounds, false)
if bigEndian {
j := blockSize - 1
for i := 0; i < blockSize/2; i++ {
s.block[j], s.block[i] = s.block[i], s.block[j]
j--
}
}
s.offset = 0
i := s.state[12] + 1
s.state[12] = i
if i == 0 {
s.state[13]++
}
}
const (
wordSize = 4 // the size of ChaCha20's words
stateSize = 16 // the size of ChaCha20's state, in words
blockSize = stateSize * wordSize // the size of ChaCha20's block, in bytes
)
var (
bigEndian bool // whether or not we're running on a bigEndian CPU
)
// Do some up-front bookkeeping on what sort of CPU we're using. ChaCha20 treats
// its state as a little-endian byte array when it comes to generating the
// keystream, which allows for a zero-copy approach to the core transform. On
// big-endian architectures, we have to take a hit to reverse the bytes.
func init() {
x := uint32(0x04030201)
y := [4]byte{0x1, 0x2, 0x3, 0x4}
bigEndian = *(*[4]byte)(unsafe.Pointer(&x)) != y
}