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 }