1// Copyright 2016 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package chacha20poly1305 6 7import ( 8 "bytes" 9 "crypto/cipher" 10 cryptorand "crypto/rand" 11 "encoding/hex" 12 "fmt" 13 mathrand "math/rand" 14 "strconv" 15 "testing" 16) 17 18func TestVectors(t *testing.T) { 19 for i, test := range chacha20Poly1305Tests { 20 key, _ := hex.DecodeString(test.key) 21 nonce, _ := hex.DecodeString(test.nonce) 22 ad, _ := hex.DecodeString(test.aad) 23 plaintext, _ := hex.DecodeString(test.plaintext) 24 25 var ( 26 aead cipher.AEAD 27 err error 28 ) 29 switch len(nonce) { 30 case NonceSize: 31 aead, err = New(key) 32 case NonceSizeX: 33 aead, err = NewX(key) 34 default: 35 t.Fatalf("#%d: wrong nonce length: %d", i, len(nonce)) 36 } 37 if err != nil { 38 t.Fatal(err) 39 } 40 41 ct := aead.Seal(nil, nonce, plaintext, ad) 42 if ctHex := hex.EncodeToString(ct); ctHex != test.out { 43 t.Errorf("#%d: got %s, want %s", i, ctHex, test.out) 44 continue 45 } 46 47 plaintext2, err := aead.Open(nil, nonce, ct, ad) 48 if err != nil { 49 t.Errorf("#%d: Open failed", i) 50 continue 51 } 52 53 if !bytes.Equal(plaintext, plaintext2) { 54 t.Errorf("#%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext) 55 continue 56 } 57 58 if len(ad) > 0 { 59 alterAdIdx := mathrand.Intn(len(ad)) 60 ad[alterAdIdx] ^= 0x80 61 if _, err := aead.Open(nil, nonce, ct, ad); err == nil { 62 t.Errorf("#%d: Open was successful after altering additional data", i) 63 } 64 ad[alterAdIdx] ^= 0x80 65 } 66 67 alterNonceIdx := mathrand.Intn(aead.NonceSize()) 68 nonce[alterNonceIdx] ^= 0x80 69 if _, err := aead.Open(nil, nonce, ct, ad); err == nil { 70 t.Errorf("#%d: Open was successful after altering nonce", i) 71 } 72 nonce[alterNonceIdx] ^= 0x80 73 74 alterCtIdx := mathrand.Intn(len(ct)) 75 ct[alterCtIdx] ^= 0x80 76 if _, err := aead.Open(nil, nonce, ct, ad); err == nil { 77 t.Errorf("#%d: Open was successful after altering ciphertext", i) 78 } 79 ct[alterCtIdx] ^= 0x80 80 } 81} 82 83func TestRandom(t *testing.T) { 84 // Some random tests to verify Open(Seal) == Plaintext 85 f := func(t *testing.T, nonceSize int) { 86 for i := 0; i < 256; i++ { 87 var nonce = make([]byte, nonceSize) 88 var key [32]byte 89 90 al := mathrand.Intn(128) 91 pl := mathrand.Intn(16384) 92 ad := make([]byte, al) 93 plaintext := make([]byte, pl) 94 cryptorand.Read(key[:]) 95 cryptorand.Read(nonce[:]) 96 cryptorand.Read(ad) 97 cryptorand.Read(plaintext) 98 99 var ( 100 aead cipher.AEAD 101 err error 102 ) 103 switch len(nonce) { 104 case NonceSize: 105 aead, err = New(key[:]) 106 case NonceSizeX: 107 aead, err = NewX(key[:]) 108 default: 109 t.Fatalf("#%d: wrong nonce length: %d", i, len(nonce)) 110 } 111 if err != nil { 112 t.Fatal(err) 113 } 114 115 ct := aead.Seal(nil, nonce[:], plaintext, ad) 116 117 plaintext2, err := aead.Open(nil, nonce[:], ct, ad) 118 if err != nil { 119 t.Errorf("Random #%d: Open failed", i) 120 continue 121 } 122 123 if !bytes.Equal(plaintext, plaintext2) { 124 t.Errorf("Random #%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext) 125 continue 126 } 127 128 if len(ad) > 0 { 129 alterAdIdx := mathrand.Intn(len(ad)) 130 ad[alterAdIdx] ^= 0x80 131 if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil { 132 t.Errorf("Random #%d: Open was successful after altering additional data", i) 133 } 134 ad[alterAdIdx] ^= 0x80 135 } 136 137 alterNonceIdx := mathrand.Intn(aead.NonceSize()) 138 nonce[alterNonceIdx] ^= 0x80 139 if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil { 140 t.Errorf("Random #%d: Open was successful after altering nonce", i) 141 } 142 nonce[alterNonceIdx] ^= 0x80 143 144 alterCtIdx := mathrand.Intn(len(ct)) 145 ct[alterCtIdx] ^= 0x80 146 if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil { 147 t.Errorf("Random #%d: Open was successful after altering ciphertext", i) 148 } 149 ct[alterCtIdx] ^= 0x80 150 } 151 } 152 t.Run("Standard", func(t *testing.T) { f(t, NonceSize) }) 153 t.Run("X", func(t *testing.T) { f(t, NonceSizeX) }) 154} 155 156func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) { 157 b.ReportAllocs() 158 b.SetBytes(int64(len(buf))) 159 160 var key [32]byte 161 var nonce = make([]byte, nonceSize) 162 var ad [13]byte 163 var out []byte 164 165 var aead cipher.AEAD 166 switch len(nonce) { 167 case NonceSize: 168 aead, _ = New(key[:]) 169 case NonceSizeX: 170 aead, _ = NewX(key[:]) 171 } 172 173 b.ResetTimer() 174 for i := 0; i < b.N; i++ { 175 out = aead.Seal(out[:0], nonce[:], buf[:], ad[:]) 176 } 177} 178 179func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) { 180 b.ReportAllocs() 181 b.SetBytes(int64(len(buf))) 182 183 var key [32]byte 184 var nonce = make([]byte, nonceSize) 185 var ad [13]byte 186 var ct []byte 187 var out []byte 188 189 var aead cipher.AEAD 190 switch len(nonce) { 191 case NonceSize: 192 aead, _ = New(key[:]) 193 case NonceSizeX: 194 aead, _ = NewX(key[:]) 195 } 196 ct = aead.Seal(ct[:0], nonce[:], buf[:], ad[:]) 197 198 b.ResetTimer() 199 for i := 0; i < b.N; i++ { 200 out, _ = aead.Open(out[:0], nonce[:], ct[:], ad[:]) 201 } 202} 203 204func BenchmarkChacha20Poly1305(b *testing.B) { 205 for _, length := range []int{64, 1350, 8 * 1024} { 206 b.Run("Open-"+strconv.Itoa(length), func(b *testing.B) { 207 benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSize) 208 }) 209 b.Run("Seal-"+strconv.Itoa(length), func(b *testing.B) { 210 benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize) 211 }) 212 213 b.Run("Open-"+strconv.Itoa(length)+"-X", func(b *testing.B) { 214 benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSizeX) 215 }) 216 b.Run("Seal-"+strconv.Itoa(length)+"-X", func(b *testing.B) { 217 benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX) 218 }) 219 } 220} 221 222func ExampleNewX() { 223 // key should be randomly generated or derived from a function like Argon2. 224 key := make([]byte, KeySize) 225 if _, err := cryptorand.Read(key); err != nil { 226 panic(err) 227 } 228 229 aead, err := NewX(key) 230 if err != nil { 231 panic(err) 232 } 233 234 // Encryption. 235 var encryptedMsg []byte 236 { 237 msg := []byte("Gophers, gophers, gophers everywhere!") 238 239 // Select a random nonce, and leave capacity for the ciphertext. 240 nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead()) 241 if _, err := cryptorand.Read(nonce); err != nil { 242 panic(err) 243 } 244 245 // Encrypt the message and append the ciphertext to the nonce. 246 encryptedMsg = aead.Seal(nonce, nonce, msg, nil) 247 } 248 249 // Decryption. 250 { 251 if len(encryptedMsg) < aead.NonceSize() { 252 panic("ciphertext too short") 253 } 254 255 // Split nonce and ciphertext. 256 nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():] 257 258 // Decrypt the message and check it wasn't tampered with. 259 plaintext, err := aead.Open(nil, nonce, ciphertext, nil) 260 if err != nil { 261 panic(err) 262 } 263 264 fmt.Printf("%s\n", plaintext) 265 } 266 267 // Output: Gophers, gophers, gophers everywhere! 268} 269