1package runner 2 3import ( 4 "crypto/cipher" 5 "crypto/subtle" 6 "encoding/binary" 7 "errors" 8 9 "./poly1305" 10) 11 12// See RFC 7539. 13 14func leftRotate(a uint32, n uint) uint32 { 15 return (a << n) | (a >> (32 - n)) 16} 17 18func chaChaQuarterRound(state *[16]uint32, a, b, c, d int) { 19 state[a] += state[b] 20 state[d] = leftRotate(state[d]^state[a], 16) 21 22 state[c] += state[d] 23 state[b] = leftRotate(state[b]^state[c], 12) 24 25 state[a] += state[b] 26 state[d] = leftRotate(state[d]^state[a], 8) 27 28 state[c] += state[d] 29 state[b] = leftRotate(state[b]^state[c], 7) 30} 31 32func chaCha20Block(state *[16]uint32, out []byte) { 33 var workingState [16]uint32 34 copy(workingState[:], state[:]) 35 for i := 0; i < 10; i++ { 36 chaChaQuarterRound(&workingState, 0, 4, 8, 12) 37 chaChaQuarterRound(&workingState, 1, 5, 9, 13) 38 chaChaQuarterRound(&workingState, 2, 6, 10, 14) 39 chaChaQuarterRound(&workingState, 3, 7, 11, 15) 40 chaChaQuarterRound(&workingState, 0, 5, 10, 15) 41 chaChaQuarterRound(&workingState, 1, 6, 11, 12) 42 chaChaQuarterRound(&workingState, 2, 7, 8, 13) 43 chaChaQuarterRound(&workingState, 3, 4, 9, 14) 44 } 45 for i := 0; i < 16; i++ { 46 binary.LittleEndian.PutUint32(out[i*4:i*4+4], workingState[i]+state[i]) 47 } 48} 49 50// sliceForAppend takes a slice and a requested number of bytes. It returns a 51// slice with the contents of the given slice followed by that many bytes and a 52// second slice that aliases into it and contains only the extra bytes. If the 53// original slice has sufficient capacity then no allocation is performed. 54func sliceForAppend(in []byte, n int) (head, tail []byte) { 55 if total := len(in) + n; cap(in) >= total { 56 head = in[:total] 57 } else { 58 head = make([]byte, total) 59 copy(head, in) 60 } 61 tail = head[len(in):] 62 return 63} 64 65func chaCha20(out, in, key, nonce []byte, counter uint64) { 66 var state [16]uint32 67 state[0] = 0x61707865 68 state[1] = 0x3320646e 69 state[2] = 0x79622d32 70 state[3] = 0x6b206574 71 for i := 0; i < 8; i++ { 72 state[4+i] = binary.LittleEndian.Uint32(key[i*4 : i*4+4]) 73 } 74 75 switch len(nonce) { 76 case 8: 77 state[14] = binary.LittleEndian.Uint32(nonce[0:4]) 78 state[15] = binary.LittleEndian.Uint32(nonce[4:8]) 79 case 12: 80 state[13] = binary.LittleEndian.Uint32(nonce[0:4]) 81 state[14] = binary.LittleEndian.Uint32(nonce[4:8]) 82 state[15] = binary.LittleEndian.Uint32(nonce[8:12]) 83 default: 84 panic("bad nonce length") 85 } 86 87 for i := 0; i < len(in); i += 64 { 88 state[12] = uint32(counter) 89 90 var tmp [64]byte 91 chaCha20Block(&state, tmp[:]) 92 count := 64 93 if len(in)-i < count { 94 count = len(in) - i 95 } 96 for j := 0; j < count; j++ { 97 out[i+j] = in[i+j] ^ tmp[j] 98 } 99 100 counter++ 101 } 102} 103 104// chaCha20Poly1305 implements the AEAD from 105// RFC 7539 and draft-agl-tls-chacha20poly1305-04. 106type chaCha20Poly1305 struct { 107 key [32]byte 108 // oldMode, if true, indicates that the draft spec should be 109 // implemented rather than the final, RFC version. 110 oldMode bool 111} 112 113func newChaCha20Poly1305(key []byte) (cipher.AEAD, error) { 114 if len(key) != 32 { 115 return nil, errors.New("bad key length") 116 } 117 aead := new(chaCha20Poly1305) 118 copy(aead.key[:], key) 119 return aead, nil 120} 121 122func newChaCha20Poly1305Old(key []byte) (cipher.AEAD, error) { 123 if len(key) != 32 { 124 return nil, errors.New("bad key length") 125 } 126 aead := &chaCha20Poly1305{ 127 oldMode: true, 128 } 129 copy(aead.key[:], key) 130 return aead, nil 131} 132 133func (c *chaCha20Poly1305) NonceSize() int { 134 if c.oldMode { 135 return 8 136 } else { 137 return 12 138 } 139} 140 141func (c *chaCha20Poly1305) Overhead() int { return 16 } 142 143func (c *chaCha20Poly1305) poly1305(tag *[16]byte, nonce, ciphertext, additionalData []byte) { 144 input := make([]byte, 0, len(additionalData)+15+len(ciphertext)+15+8+8) 145 input = append(input, additionalData...) 146 var zeros [15]byte 147 if pad := len(input) % 16; pad != 0 { 148 input = append(input, zeros[:16-pad]...) 149 } 150 input = append(input, ciphertext...) 151 if pad := len(input) % 16; pad != 0 { 152 input = append(input, zeros[:16-pad]...) 153 } 154 input, out := sliceForAppend(input, 8) 155 binary.LittleEndian.PutUint64(out, uint64(len(additionalData))) 156 input, out = sliceForAppend(input, 8) 157 binary.LittleEndian.PutUint64(out, uint64(len(ciphertext))) 158 159 var poly1305Key [32]byte 160 chaCha20(poly1305Key[:], poly1305Key[:], c.key[:], nonce, 0) 161 162 poly1305.Sum(tag, input, &poly1305Key) 163} 164 165func (c *chaCha20Poly1305) poly1305Old(tag *[16]byte, nonce, ciphertext, additionalData []byte) { 166 input := make([]byte, 0, len(additionalData)+8+len(ciphertext)+8) 167 input = append(input, additionalData...) 168 input, out := sliceForAppend(input, 8) 169 binary.LittleEndian.PutUint64(out, uint64(len(additionalData))) 170 input = append(input, ciphertext...) 171 input, out = sliceForAppend(input, 8) 172 binary.LittleEndian.PutUint64(out, uint64(len(ciphertext))) 173 174 var poly1305Key [32]byte 175 chaCha20(poly1305Key[:], poly1305Key[:], c.key[:], nonce, 0) 176 177 poly1305.Sum(tag, input, &poly1305Key) 178} 179 180func (c *chaCha20Poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { 181 if len(nonce) != c.NonceSize() { 182 panic("Bad nonce length") 183 } 184 185 ret, out := sliceForAppend(dst, len(plaintext)+16) 186 chaCha20(out[:len(plaintext)], plaintext, c.key[:], nonce, 1) 187 188 var tag [16]byte 189 if c.oldMode { 190 c.poly1305Old(&tag, nonce, out[:len(plaintext)], additionalData) 191 } else { 192 c.poly1305(&tag, nonce, out[:len(plaintext)], additionalData) 193 } 194 copy(out[len(plaintext):], tag[:]) 195 196 return ret 197} 198 199func (c *chaCha20Poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { 200 if len(nonce) != c.NonceSize() { 201 panic("Bad nonce length") 202 } 203 if len(ciphertext) < 16 { 204 return nil, errors.New("chacha20: message authentication failed") 205 } 206 plaintextLen := len(ciphertext) - 16 207 208 var tag [16]byte 209 if c.oldMode { 210 c.poly1305Old(&tag, nonce, ciphertext[:plaintextLen], additionalData) 211 } else { 212 c.poly1305(&tag, nonce, ciphertext[:plaintextLen], additionalData) 213 } 214 if subtle.ConstantTimeCompare(tag[:], ciphertext[plaintextLen:]) != 1 { 215 return nil, errors.New("chacha20: message authentication failed") 216 } 217 218 ret, out := sliceForAppend(dst, plaintextLen) 219 chaCha20(out, ciphertext[:plaintextLen], c.key[:], nonce, 1) 220 return ret, nil 221} 222