git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/crypto/chacha/chacha.go (about)

     1  // Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
     2  // Use of this source code is governed by a license that can be
     3  // found in the LICENSE file.
     4  
     5  // Package chacha implements some low-level functions of the
     6  // ChaCha cipher family.
     7  package chacha // import "github.com/aead/chacha20/chacha"
     8  
     9  import (
    10  	"encoding/binary"
    11  	"errors"
    12  	"math"
    13  )
    14  
    15  const (
    16  	// NonceSize is the size of the ChaCha20 nonce in bytes.
    17  	NonceSize = 8
    18  
    19  	// INonceSize is the size of the IETF-ChaCha20 nonce in bytes.
    20  	INonceSize = 12
    21  
    22  	// XNonceSize is the size of the XChaCha20 nonce in bytes.
    23  	XNonceSize = 24
    24  
    25  	// KeySize is the size of the key in bytes.
    26  	KeySize = 32
    27  )
    28  
    29  var chachaConstants = [...]uint32{0x61707865, 0x3320646e, 0x79622d32, 0x6b206574}
    30  
    31  var (
    32  	useSSE2  bool
    33  	useSSSE3 bool
    34  	useAVX   bool
    35  	useAVX2  bool
    36  )
    37  
    38  var (
    39  	ErrKeySize               = errors.New("chacha20/chacha: bad key length")
    40  	ErrInvalidNonce          = errors.New("chacha20/chacha: bad nonce length")
    41  	ErrInvalidNumberOfRounds = errors.New("chacha: invalid number of rounds")
    42  )
    43  
    44  func setup(state *[64]byte, nonce, key []byte) (err error) {
    45  	if len(key) != KeySize {
    46  		err = ErrKeySize
    47  		return
    48  	}
    49  	var Nonce [16]byte
    50  	switch len(nonce) {
    51  	case NonceSize:
    52  		copy(Nonce[8:], nonce)
    53  		initialize(state, key, &Nonce)
    54  	case INonceSize:
    55  		copy(Nonce[4:], nonce)
    56  		initialize(state, key, &Nonce)
    57  	case XNonceSize:
    58  		var tmpKey [32]byte
    59  		var hNonce [16]byte
    60  
    61  		copy(hNonce[:], nonce[:16])
    62  		copy(tmpKey[:], key)
    63  		hChaCha20(tmpKey[:], tmpKey[:], hNonce[:])
    64  		copy(Nonce[8:], nonce[16:])
    65  		initialize(state, tmpKey[:], &Nonce)
    66  
    67  		// BUG(aead): A "good" compiler will remove this (optimizations)
    68  		//			  But using the provided key instead of tmpKey,
    69  		//			  will change the key (-> probably confuses users)
    70  		for i := range tmpKey {
    71  			tmpKey[i] = 0
    72  		}
    73  	default:
    74  		err = ErrInvalidNonce
    75  	}
    76  	return
    77  }
    78  
    79  // XORKeyStream crypts bytes from src to dst using the given nonce and key.
    80  // The length of the nonce determinds the version of ChaCha20:
    81  // - NonceSize:  ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period.
    82  // - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period.
    83  // - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period.
    84  // The rounds argument specifies the number of rounds performed for keystream
    85  // generation - valid values are 8, 12 or 20. The src and dst may be the same slice
    86  // but otherwise should not overlap. If len(dst) < len(src) this function panics.
    87  // If the nonce is neither 64, 96 nor 192 bits long, this function panics.
    88  // func XORKeyStream(dst, src, nonce, key []byte, rounds int) {
    89  // 	if rounds != 20 && rounds != 12 && rounds != 8 {
    90  // 		panic("chacha20/chacha: bad number of rounds")
    91  // 	}
    92  // 	if len(dst) < len(src) {
    93  // 		panic("chacha20/chacha: dst buffer is to small")
    94  // 	}
    95  // 	if len(nonce) == INonceSize && uint64(len(src)) > (1<<38) {
    96  // 		panic("chacha20/chacha: src is too large")
    97  // 	}
    98  
    99  // 	var block, state [64]byte
   100  // 	if err := setup(&state, nonce, key); err != nil {
   101  // 		panic(err)
   102  // 	}
   103  // 	xorKeyStream(dst, src, &block, &state, rounds)
   104  // }
   105  
   106  // Cipher implements ChaCha20/r (XChaCha20/r) for a given number of rounds r.
   107  type Cipher struct {
   108  	state, block [64]byte
   109  	off          int
   110  	rounds       int // 20 for ChaCha20
   111  	noncesize    int
   112  }
   113  
   114  // NewCipher returns a new *chacha.Cipher implementing the ChaCha20/r or XChaCha20/r
   115  // (r = 8, 12 or 20) stream cipher. The nonce must be unique for one key for all time.
   116  // The length of the nonce determinds the version of ChaCha20:
   117  // - NonceSize:  ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period.
   118  // - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period.
   119  // - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period.
   120  // If the nonce is neither 64, 96 nor 192 bits long, a non-nil error is returned.
   121  func NewCipher(nonce, key []byte, rounds int) (*Cipher, error) {
   122  	if rounds != 20 && rounds != 12 && rounds != 8 {
   123  		return nil, ErrInvalidNumberOfRounds
   124  	}
   125  
   126  	c := new(Cipher)
   127  	if err := setup(&(c.state), nonce, key); err != nil {
   128  		return nil, err
   129  	}
   130  	c.rounds = rounds
   131  
   132  	if len(nonce) == INonceSize {
   133  		c.noncesize = INonceSize
   134  	} else {
   135  		c.noncesize = NonceSize
   136  	}
   137  
   138  	return c, nil
   139  }
   140  
   141  // XORKeyStream crypts bytes from src to dst. Src and dst may be the same slice
   142  // but otherwise should not overlap. If len(dst) < len(src) the function panics.
   143  func (c *Cipher) XORKeyStream(dst, src []byte) {
   144  	if len(dst) < len(src) {
   145  		panic("chacha20/chacha: dst buffer is to small")
   146  	}
   147  
   148  	if c.off > 0 {
   149  		n := len(c.block[c.off:])
   150  		if len(src) <= n {
   151  			for i, v := range src {
   152  				dst[i] = v ^ c.block[c.off]
   153  				c.off++
   154  			}
   155  			if c.off == 64 {
   156  				c.off = 0
   157  			}
   158  			return
   159  		}
   160  
   161  		for i, v := range c.block[c.off:] {
   162  			dst[i] = src[i] ^ v
   163  		}
   164  		src = src[n:]
   165  		dst = dst[n:]
   166  		c.off = 0
   167  	}
   168  
   169  	// check for counter overflow
   170  	blocksToXOR := len(src) / 64
   171  	if len(src)%64 != 0 {
   172  		blocksToXOR++
   173  	}
   174  	var overflow bool
   175  	if c.noncesize == INonceSize {
   176  		overflow = binary.LittleEndian.Uint32(c.state[48:]) > math.MaxUint32-uint32(blocksToXOR)
   177  	} else {
   178  		overflow = binary.LittleEndian.Uint64(c.state[48:]) > math.MaxUint64-uint64(blocksToXOR)
   179  	}
   180  	if overflow {
   181  		panic("chacha20/chacha: counter overflow")
   182  	}
   183  
   184  	c.off += xorKeyStream(dst, src, &(c.block), &(c.state), c.rounds)
   185  }
   186  
   187  // SetCounter skips ctr * 64 byte blocks. SetCounter(0) resets the cipher.
   188  // This function always skips the unused keystream of the current 64 byte block.
   189  // We use uint32 by default for compatibility reasons with /x/crypto/chacha20
   190  // See SetCounterUint64 if you have a specific need
   191  func (c *Cipher) SetCounter(ctr uint32) {
   192  	if c.noncesize == INonceSize {
   193  		binary.LittleEndian.PutUint32(c.state[48:], ctr)
   194  	} else {
   195  		binary.LittleEndian.PutUint64(c.state[48:], uint64(ctr))
   196  	}
   197  	c.off = 0
   198  }
   199  
   200  // SetCounter skips ctr * 64 byte blocks. SetCounter(0) resets the cipher.
   201  // This function always skips the unused keystream of the current 64 byte block.
   202  func (c *Cipher) SetCounterUint64(ctr uint64) {
   203  	if c.noncesize == INonceSize {
   204  		binary.LittleEndian.PutUint32(c.state[48:], uint32(ctr))
   205  	} else {
   206  		binary.LittleEndian.PutUint64(c.state[48:], ctr)
   207  	}
   208  	c.off = 0
   209  }