gitlab.com/yawning/chacha20.git@v0.0.0-20230427033715-7877545b1b37/chacha20.go (about)

     1  // Copryright (C) 2019 Yawning Angel
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  // Package chacha20 implements the ChaCha20 stream cipher.
    17  package chacha20 // import "gitlab.com/yawning/chacha20.git"
    18  
    19  import (
    20  	"crypto/cipher"
    21  	"encoding/binary"
    22  	"errors"
    23  	"math"
    24  
    25  	"gitlab.com/yawning/chacha20.git/internal/api"
    26  	"gitlab.com/yawning/chacha20.git/internal/hardware"
    27  	"gitlab.com/yawning/chacha20.git/internal/ref"
    28  )
    29  
    30  const (
    31  	// KeySize is the ChaCha20 key size in bytes.
    32  	KeySize = 32
    33  
    34  	// NonceSize is the ChaCha20 nonce size in bytes.
    35  	NonceSize = 8
    36  
    37  	// INonceSize is the IETF ChaCha20 nonce size in bytes.
    38  	INonceSize = 12
    39  
    40  	// XNonceSize is the XChaCha20 nonce size in bytes.
    41  	XNonceSize = 24
    42  
    43  	// HNonceSize is the HChaCha20 nonce size in bytes.
    44  	HNonceSize = 16
    45  )
    46  
    47  var (
    48  	// ErrInvalidKey is the error returned when the key is invalid.
    49  	ErrInvalidKey = errors.New("chacha20: key length must be KeySize bytes")
    50  
    51  	// ErrInvalidNonce is the error returned when the nonce is invalid.
    52  	ErrInvalidNonce = errors.New("chacha20: nonce length must be NonceSize/INonceSize/XNonceSize bytes")
    53  
    54  	// ErrInvalidCounter is the error returned when the counter is invalid.
    55  	ErrInvalidCounter = errors.New("chacha20: block counter is invalid (out of range)")
    56  
    57  	supportedImpls []api.Implementation
    58  	activeImpl     api.Implementation
    59  
    60  	_ cipher.Stream = (*Cipher)(nil)
    61  )
    62  
    63  // Cipher is an instance of ChaCha20/XChaCha20 using a particular key and nonce.
    64  type Cipher struct {
    65  	state [api.StateSize]uint32
    66  	buf   [api.BlockSize]byte
    67  
    68  	off  int
    69  	ietf bool
    70  }
    71  
    72  // Reset zeros the key data so that it will no longer appear in the process's
    73  // memory.
    74  func (c *Cipher) Reset() {
    75  	for i := range c.state {
    76  		c.state[i] = 0
    77  	}
    78  	for i := range c.buf {
    79  		c.buf[i] = 0
    80  	}
    81  }
    82  
    83  // Seek sets the block counter to a given offset.
    84  func (c *Cipher) Seek(blockCounter uint64) error {
    85  	if c.ietf {
    86  		if blockCounter > math.MaxUint32 {
    87  			return ErrInvalidCounter
    88  		}
    89  		c.state[12] = uint32(blockCounter)
    90  	} else {
    91  		c.state[12] = uint32(blockCounter)
    92  		c.state[13] = uint32(blockCounter >> 32)
    93  	}
    94  	c.off = api.BlockSize
    95  	return nil
    96  }
    97  
    98  // ReKey reinitializes the ChaCha20/XChaCha20 instance with the provided key
    99  // and nonce.
   100  func (c *Cipher) ReKey(key, nonce []byte) error {
   101  	c.Reset()
   102  	return c.doReKey(key, nonce)
   103  }
   104  
   105  func (c *Cipher) doReKey(key, nonce []byte) error {
   106  	if len(key) != KeySize {
   107  		return ErrInvalidKey
   108  	}
   109  
   110  	var subKey []byte
   111  	switch len(nonce) {
   112  	case NonceSize, INonceSize:
   113  	case XNonceSize:
   114  		subKey = c.buf[:KeySize]
   115  		activeImpl.HChaCha(key, nonce, subKey)
   116  		key = subKey
   117  		nonce = nonce[16:24]
   118  	default:
   119  		return ErrInvalidNonce
   120  	}
   121  
   122  	_ = key[31] // Force bounds check elimination.
   123  
   124  	c.state[0] = api.Sigma0
   125  	c.state[1] = api.Sigma1
   126  	c.state[2] = api.Sigma2
   127  	c.state[3] = api.Sigma3
   128  	c.state[4] = binary.LittleEndian.Uint32(key[0:4])
   129  	c.state[5] = binary.LittleEndian.Uint32(key[4:8])
   130  	c.state[6] = binary.LittleEndian.Uint32(key[8:12])
   131  	c.state[7] = binary.LittleEndian.Uint32(key[12:16])
   132  	c.state[8] = binary.LittleEndian.Uint32(key[16:20])
   133  	c.state[9] = binary.LittleEndian.Uint32(key[20:24])
   134  	c.state[10] = binary.LittleEndian.Uint32(key[24:28])
   135  	c.state[11] = binary.LittleEndian.Uint32(key[28:32])
   136  	c.state[12] = 0
   137  	if len(nonce) == INonceSize {
   138  		_ = nonce[11] // Force bounds check elimination.
   139  		c.state[13] = binary.LittleEndian.Uint32(nonce[0:4])
   140  		c.state[14] = binary.LittleEndian.Uint32(nonce[4:8])
   141  		c.state[15] = binary.LittleEndian.Uint32(nonce[8:12])
   142  		c.ietf = true
   143  	} else {
   144  		_ = nonce[7] // Force bounds check elimination.
   145  		c.state[13] = 0
   146  		c.state[14] = binary.LittleEndian.Uint32(nonce[0:4])
   147  		c.state[15] = binary.LittleEndian.Uint32(nonce[4:8])
   148  		c.ietf = false
   149  	}
   150  	c.off = api.BlockSize
   151  
   152  	if subKey != nil {
   153  		for i := range subKey {
   154  			subKey[i] = 0
   155  		}
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  // New returns a new ChaCha20/XChaCha20 instance.
   162  func New(key, nonce []byte) (*Cipher, error) {
   163  	var c Cipher
   164  	if err := c.doReKey(key, nonce); err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	return &c, nil
   169  }
   170  
   171  // HChaCha is the HChaCha20 hash function used to make XChaCha.
   172  func HChaCha(key, nonce []byte, dst *[32]byte) {
   173  	activeImpl.HChaCha(key, nonce, dst[:])
   174  }
   175  
   176  // XORKeyStream sets dst to the result of XORing src with the key stream.  Dst
   177  // and src may be the same slice but otherwise should not overlap.
   178  func (c *Cipher) XORKeyStream(dst, src []byte) {
   179  	if len(dst) < len(src) {
   180  		src = src[:len(dst)]
   181  	}
   182  
   183  	for remaining := len(src); remaining > 0; {
   184  		// Process multiple blocks at once.
   185  		if c.off == api.BlockSize {
   186  			nrBlocks := remaining / api.BlockSize
   187  			directBytes := nrBlocks * api.BlockSize
   188  			if nrBlocks > 0 {
   189  				c.doBlocks(dst, src, nrBlocks)
   190  				remaining -= directBytes
   191  				if remaining == 0 {
   192  					return
   193  				}
   194  				dst = dst[directBytes:]
   195  				src = src[directBytes:]
   196  			}
   197  
   198  			// If there's a partial block, generate 1 block of keystream into
   199  			// the internal buffer.
   200  			c.doBlocks(c.buf[:], nil, 1)
   201  			c.off = 0
   202  		}
   203  
   204  		// Process partial blocks from the buffered keystream.
   205  		toXor := api.BlockSize - c.off
   206  		if remaining < toXor {
   207  			toXor = remaining
   208  		}
   209  		if toXor > 0 {
   210  			// The inliner doesn't want to inline this function, but my
   211  			// attempts to force BCE don't seem to work with manual
   212  			// inlining.
   213  			//
   214  			// Taking the extra function call overhead here appears to be
   215  			// worth it.
   216  			c.xorBufBytes(dst, src, toXor)
   217  
   218  			dst = dst[toXor:]
   219  			src = src[toXor:]
   220  
   221  			remaining -= toXor
   222  		}
   223  	}
   224  }
   225  
   226  func (c *Cipher) xorBufBytes(dst, src []byte, n int) {
   227  	// Force bounds check elimination.
   228  	buf := c.buf[c.off:]
   229  	_ = buf[n-1]
   230  	_ = dst[n-1]
   231  	_ = src[n-1]
   232  
   233  	for i := 0; i < n; i++ {
   234  		dst[i] = buf[i] ^ src[i]
   235  	}
   236  	c.off += n
   237  }
   238  
   239  // KeyStream sets dst to the raw keystream.
   240  func (c *Cipher) KeyStream(dst []byte) {
   241  	for remaining := len(dst); remaining > 0; {
   242  		// Process multiple blocks at once.
   243  		if c.off == api.BlockSize {
   244  			nrBlocks := remaining / api.BlockSize
   245  			directBytes := nrBlocks * api.BlockSize
   246  			if nrBlocks > 0 {
   247  				c.doBlocks(dst, nil, nrBlocks)
   248  				remaining -= directBytes
   249  				if remaining == 0 {
   250  					return
   251  				}
   252  				dst = dst[directBytes:]
   253  			}
   254  
   255  			// If there's a partial block, generate 1 block of keystream into
   256  			// the internal buffer.
   257  			c.doBlocks(c.buf[:], nil, 1)
   258  			c.off = 0
   259  		}
   260  
   261  		// Process partial blocks from the buffered keystream.
   262  		toCopy := api.BlockSize - c.off
   263  		if remaining < toCopy {
   264  			toCopy = remaining
   265  		}
   266  		if toCopy > 0 {
   267  			copy(dst[:toCopy], c.buf[c.off:c.off+toCopy])
   268  			dst = dst[toCopy:]
   269  			remaining -= toCopy
   270  			c.off += toCopy
   271  		}
   272  	}
   273  }
   274  
   275  func (c *Cipher) doBlocks(dst, src []byte, nrBlocks int) {
   276  	if c.ietf {
   277  		ctr := uint64(c.state[12])
   278  		if ctr+uint64(nrBlocks) > math.MaxUint32 {
   279  			panic("chacha20: will exceed key stream per nonce limit")
   280  		}
   281  	}
   282  
   283  	activeImpl.Blocks(&c.state, dst, src, nrBlocks)
   284  }
   285  
   286  func init() {
   287  	supportedImpls = hardware.Register(supportedImpls)
   288  	supportedImpls = ref.Register(supportedImpls)
   289  	activeImpl = supportedImpls[0]
   290  }