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