github.com/psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/crypto/Yawning/chacha20/chacha20.go (about)

     1  // chacha20.go - A ChaCha stream cipher implementation.
     2  //
     3  // To the extent possible under law, Yawning Angel has waived all copyright
     4  // and related or neighboring rights to chacha20, using the Creative
     5  // Commons "CC0" public domain dedication. See LICENSE or
     6  // <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
     7  
     8  package chacha20
     9  
    10  import (
    11  	"crypto/cipher"
    12  	"encoding/binary"
    13  	"errors"
    14  	"math"
    15  	"runtime"
    16  )
    17  
    18  const (
    19  	// KeySize is the ChaCha20 key size in bytes.
    20  	KeySize = 32
    21  
    22  	// NonceSize is the ChaCha20 nonce size in bytes.
    23  	NonceSize = 8
    24  
    25  	// INonceSize is the IETF ChaCha20 nonce size in bytes.
    26  	INonceSize = 12
    27  
    28  	// XNonceSize is the XChaCha20 nonce size in bytes.
    29  	XNonceSize = 24
    30  
    31  	// HNonceSize is the HChaCha20 nonce size in bytes.
    32  	HNonceSize = 16
    33  
    34  	// BlockSize is the ChaCha20 block size in bytes.
    35  	BlockSize = 64
    36  
    37  	stateSize    = 16
    38  	chachaRounds = 20
    39  
    40  	// The constant "expand 32-byte k" as little endian uint32s.
    41  	sigma0 = uint32(0x61707865)
    42  	sigma1 = uint32(0x3320646e)
    43  	sigma2 = uint32(0x79622d32)
    44  	sigma3 = uint32(0x6b206574)
    45  )
    46  
    47  var (
    48  	// ErrInvalidKey is the error returned when the key is invalid.
    49  	ErrInvalidKey = errors.New("key length must be KeySize bytes")
    50  
    51  	// ErrInvalidNonce is the error returned when the nonce is invalid.
    52  	ErrInvalidNonce = errors.New("nonce length must be NonceSize/INonceSize/XNonceSize bytes")
    53  
    54  	// ErrInvalidCounter is the error returned when the counter is invalid.
    55  	ErrInvalidCounter = errors.New("block counter is invalid (out of range)")
    56  
    57  	useUnsafe    = false
    58  	usingVectors = false
    59  	blocksFn     = blocksRef
    60  )
    61  
    62  // A Cipher is an instance of ChaCha20/XChaCha20 using a particular key and
    63  // nonce.
    64  type Cipher struct {
    65  	state [stateSize]uint32
    66  
    67  	buf  [BlockSize]byte
    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  // XORKeyStream sets dst to the result of XORing src with the key stream.  Dst
    84  // and src may be the same slice but otherwise should not overlap.
    85  func (c *Cipher) XORKeyStream(dst, src []byte) {
    86  	if len(dst) < len(src) {
    87  		src = src[:len(dst)]
    88  	}
    89  
    90  	for remaining := len(src); remaining > 0; {
    91  		// Process multiple blocks at once.
    92  		if c.off == BlockSize {
    93  			nrBlocks := remaining / BlockSize
    94  			directBytes := nrBlocks * BlockSize
    95  			if nrBlocks > 0 {
    96  				blocksFn(&c.state, src, dst, nrBlocks, c.ietf)
    97  				remaining -= directBytes
    98  				if remaining == 0 {
    99  					return
   100  				}
   101  				dst = dst[directBytes:]
   102  				src = src[directBytes:]
   103  			}
   104  
   105  			// If there's a partial block, generate 1 block of keystream into
   106  			// the internal buffer.
   107  			blocksFn(&c.state, nil, c.buf[:], 1, c.ietf)
   108  			c.off = 0
   109  		}
   110  
   111  		// Process partial blocks from the buffered keystream.
   112  		toXor := BlockSize - c.off
   113  		if remaining < toXor {
   114  			toXor = remaining
   115  		}
   116  		if toXor > 0 {
   117  			for i, v := range src[:toXor] {
   118  				dst[i] = v ^ c.buf[c.off+i]
   119  			}
   120  			dst = dst[toXor:]
   121  			src = src[toXor:]
   122  
   123  			remaining -= toXor
   124  			c.off += toXor
   125  		}
   126  	}
   127  }
   128  
   129  // KeyStream sets dst to the raw keystream.
   130  func (c *Cipher) KeyStream(dst []byte) {
   131  	for remaining := len(dst); remaining > 0; {
   132  		// Process multiple blocks at once.
   133  		if c.off == BlockSize {
   134  			nrBlocks := remaining / BlockSize
   135  			directBytes := nrBlocks * BlockSize
   136  			if nrBlocks > 0 {
   137  				blocksFn(&c.state, nil, dst, nrBlocks, c.ietf)
   138  				remaining -= directBytes
   139  				if remaining == 0 {
   140  					return
   141  				}
   142  				dst = dst[directBytes:]
   143  			}
   144  
   145  			// If there's a partial block, generate 1 block of keystream into
   146  			// the internal buffer.
   147  			blocksFn(&c.state, nil, c.buf[:], 1, c.ietf)
   148  			c.off = 0
   149  		}
   150  
   151  		// Process partial blocks from the buffered keystream.
   152  		toCopy := BlockSize - c.off
   153  		if remaining < toCopy {
   154  			toCopy = remaining
   155  		}
   156  		if toCopy > 0 {
   157  			copy(dst[:toCopy], c.buf[c.off:c.off+toCopy])
   158  			dst = dst[toCopy:]
   159  			remaining -= toCopy
   160  			c.off += toCopy
   161  		}
   162  	}
   163  }
   164  
   165  // ReKey reinitializes the ChaCha20/XChaCha20 instance with the provided key
   166  // and nonce.
   167  func (c *Cipher) ReKey(key, nonce []byte) error {
   168  	if len(key) != KeySize {
   169  		return ErrInvalidKey
   170  	}
   171  
   172  	switch len(nonce) {
   173  	case NonceSize:
   174  	case INonceSize:
   175  	case XNonceSize:
   176  		var subkey [KeySize]byte
   177  		var subnonce [HNonceSize]byte
   178  		copy(subnonce[:], nonce[0:16])
   179  		HChaCha(key, &subnonce, &subkey)
   180  		key = subkey[:]
   181  		nonce = nonce[16:24]
   182  		defer func() {
   183  			for i := range subkey {
   184  				subkey[i] = 0
   185  			}
   186  		}()
   187  	default:
   188  		return ErrInvalidNonce
   189  	}
   190  
   191  	c.Reset()
   192  	c.state[0] = sigma0
   193  	c.state[1] = sigma1
   194  	c.state[2] = sigma2
   195  	c.state[3] = sigma3
   196  	c.state[4] = binary.LittleEndian.Uint32(key[0:4])
   197  	c.state[5] = binary.LittleEndian.Uint32(key[4:8])
   198  	c.state[6] = binary.LittleEndian.Uint32(key[8:12])
   199  	c.state[7] = binary.LittleEndian.Uint32(key[12:16])
   200  	c.state[8] = binary.LittleEndian.Uint32(key[16:20])
   201  	c.state[9] = binary.LittleEndian.Uint32(key[20:24])
   202  	c.state[10] = binary.LittleEndian.Uint32(key[24:28])
   203  	c.state[11] = binary.LittleEndian.Uint32(key[28:32])
   204  	c.state[12] = 0
   205  	if len(nonce) == INonceSize {
   206  		c.state[13] = binary.LittleEndian.Uint32(nonce[0:4])
   207  		c.state[14] = binary.LittleEndian.Uint32(nonce[4:8])
   208  		c.state[15] = binary.LittleEndian.Uint32(nonce[8:12])
   209  		c.ietf = true
   210  	} else {
   211  		c.state[13] = 0
   212  		c.state[14] = binary.LittleEndian.Uint32(nonce[0:4])
   213  		c.state[15] = binary.LittleEndian.Uint32(nonce[4:8])
   214  		c.ietf = false
   215  	}
   216  	c.off = BlockSize
   217  	return nil
   218  
   219  }
   220  
   221  // Seek sets the block counter to a given offset.
   222  func (c *Cipher) Seek(blockCounter uint64) error {
   223  	if c.ietf {
   224  		if blockCounter > math.MaxUint32 {
   225  			return ErrInvalidCounter
   226  		}
   227  		c.state[12] = uint32(blockCounter)
   228  	} else {
   229  		c.state[12] = uint32(blockCounter)
   230  		c.state[13] = uint32(blockCounter >> 32)
   231  	}
   232  	c.off = BlockSize
   233  	return nil
   234  }
   235  
   236  // NewCipher returns a new ChaCha20/XChaCha20 instance.
   237  func NewCipher(key, nonce []byte) (*Cipher, error) {
   238  	c := new(Cipher)
   239  	if err := c.ReKey(key, nonce); err != nil {
   240  		return nil, err
   241  	}
   242  	return c, nil
   243  }
   244  
   245  // HChaCha is the HChaCha20 hash function used to make XChaCha.
   246  func HChaCha(key []byte, nonce *[HNonceSize]byte, out *[32]byte) {
   247  	var x [stateSize]uint32 // Last 4 slots unused, sigma hardcoded.
   248  	x[0] = binary.LittleEndian.Uint32(key[0:4])
   249  	x[1] = binary.LittleEndian.Uint32(key[4:8])
   250  	x[2] = binary.LittleEndian.Uint32(key[8:12])
   251  	x[3] = binary.LittleEndian.Uint32(key[12:16])
   252  	x[4] = binary.LittleEndian.Uint32(key[16:20])
   253  	x[5] = binary.LittleEndian.Uint32(key[20:24])
   254  	x[6] = binary.LittleEndian.Uint32(key[24:28])
   255  	x[7] = binary.LittleEndian.Uint32(key[28:32])
   256  	x[8] = binary.LittleEndian.Uint32(nonce[0:4])
   257  	x[9] = binary.LittleEndian.Uint32(nonce[4:8])
   258  	x[10] = binary.LittleEndian.Uint32(nonce[8:12])
   259  	x[11] = binary.LittleEndian.Uint32(nonce[12:16])
   260  	hChaChaRef(&x, out)
   261  }
   262  
   263  func init() {
   264  	switch runtime.GOARCH {
   265  	case "386", "amd64":
   266  		// Abuse unsafe to skip calling binary.LittleEndian.PutUint32
   267  		// in the critical path.  This is a big boost on systems that are
   268  		// little endian and not overly picky about alignment.
   269  		useUnsafe = true
   270  	}
   271  }
   272  
   273  var _ cipher.Stream = (*Cipher)(nil)