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 }