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