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)