Skip to content

Commit f792edd

Browse files
committed
chacha20poly1305: add XChaCha20-Poly1305
The XChaCha20 construction does not have an authoritative spec, but this implementation is based on the following documents: https://cr.yp.to/snuffle/xsalsa-20081128.pdf https://download.libsodium.org/doc/secret-key_cryptography/aead.html http://loup-vaillant.fr/tutorials/chacha20-design https://tools.ietf.org/html/draft-paragon-paseto-rfc-00#section-7 Tested against the following implementations: https://github.com/jedisct1/libsodium/blob/7cdf3f0e841/test/default/aead_xchacha20poly1305.c https://git.kernel.org/pub/scm/linux/kernel/git/zx2c4/linux.git/diff/lib/zinc/selftest/chacha20poly1305.h?h=zinc https://git.zx2c4.com/wireguard-go/tree/xchacha20poly1305/xchacha20.go name time/op speed Chacha20Poly1305/Open-64-8 225ns ± 1% 283MB/s ± 1% Chacha20Poly1305/Open-64-X-8 390ns ± 0% 164MB/s ± 0% Chacha20Poly1305/Seal-64-8 222ns ± 0% 287MB/s ± 0% Chacha20Poly1305/Seal-64-X-8 386ns ± 0% 165MB/s ± 1% Chacha20Poly1305/Open-1350-8 1.12µs ± 1% 1.21GB/s ± 1% Chacha20Poly1305/Open-1350-X-8 1.28µs ± 0% 1.05GB/s ± 0% Chacha20Poly1305/Seal-1350-8 1.15µs ± 0% 1.17GB/s ± 0% Chacha20Poly1305/Seal-1350-X-8 1.32µs ± 1% 1.02GB/s ± 0% Chacha20Poly1305/Open-8192-8 5.53µs ± 0% 1.48GB/s ± 0% Chacha20Poly1305/Open-8192-X-8 5.71µs ± 1% 1.44GB/s ± 1% Chacha20Poly1305/Seal-8192-8 5.54µs ± 1% 1.48GB/s ± 1% Chacha20Poly1305/Seal-8192-X-8 5.74µs ± 1% 1.43GB/s ± 1% Updates golang/go#24485 Change-Id: Iea6f3b4c2be67f16f56720a200dcc895c0f9d520 Reviewed-on: https://go-review.googlesource.com/127819 Run-TryBot: Filippo Valsorda <[email protected]> Reviewed-by: Adam Langley <[email protected]>
1 parent 56440b8 commit f792edd

File tree

7 files changed

+357
-118
lines changed

7 files changed

+357
-118
lines changed

chacha20poly1305/chacha20poly1305.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@ import (
1414
const (
1515
// KeySize is the size of the key used by this AEAD, in bytes.
1616
KeySize = 32
17-
// NonceSize is the size of the nonce used with this AEAD, in bytes.
17+
// NonceSize is the size of the nonce used with the standard variant of this
18+
// AEAD, in bytes.
19+
//
20+
// Note that this is too short to be safely generated at random if the same
21+
// key is reused more than 2³² times.
1822
NonceSize = 12
1923
)
2024

2125
type chacha20poly1305 struct {
2226
key [8]uint32
2327
}
2428

25-
// New returns a ChaCha20-Poly1305 AEAD that uses the given, 256-bit key.
29+
// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
2630
func New(key []byte) (cipher.AEAD, error) {
2731
if len(key) != KeySize {
2832
return nil, errors.New("chacha20poly1305: bad key length")

chacha20poly1305/chacha20poly1305_test.go

Lines changed: 111 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ package chacha20poly1305
66

77
import (
88
"bytes"
9+
"crypto/cipher"
910
cr "crypto/rand"
1011
"encoding/hex"
1112
mr "math/rand"
13+
"strconv"
1214
"testing"
1315
)
1416

@@ -19,7 +21,18 @@ func TestVectors(t *testing.T) {
1921
ad, _ := hex.DecodeString(test.aad)
2022
plaintext, _ := hex.DecodeString(test.plaintext)
2123

22-
aead, err := New(key)
24+
var (
25+
aead cipher.AEAD
26+
err error
27+
)
28+
switch len(nonce) {
29+
case NonceSize:
30+
aead, err = New(key)
31+
case NonceSizeX:
32+
aead, err = NewX(key)
33+
default:
34+
t.Fatalf("#%d: wrong nonce length: %d", i, len(nonce))
35+
}
2336
if err != nil {
2437
t.Fatal(err)
2538
}
@@ -68,87 +81,117 @@ func TestVectors(t *testing.T) {
6881

6982
func TestRandom(t *testing.T) {
7083
// Some random tests to verify Open(Seal) == Plaintext
71-
for i := 0; i < 256; i++ {
72-
var nonce [12]byte
73-
var key [32]byte
74-
75-
al := mr.Intn(128)
76-
pl := mr.Intn(16384)
77-
ad := make([]byte, al)
78-
plaintext := make([]byte, pl)
79-
cr.Read(key[:])
80-
cr.Read(nonce[:])
81-
cr.Read(ad)
82-
cr.Read(plaintext)
83-
84-
aead, err := New(key[:])
85-
if err != nil {
86-
t.Fatal(err)
87-
}
84+
f := func(t *testing.T, nonceSize int) {
85+
for i := 0; i < 256; i++ {
86+
var nonce = make([]byte, nonceSize)
87+
var key [32]byte
88+
89+
al := mr.Intn(128)
90+
pl := mr.Intn(16384)
91+
ad := make([]byte, al)
92+
plaintext := make([]byte, pl)
93+
cr.Read(key[:])
94+
cr.Read(nonce[:])
95+
cr.Read(ad)
96+
cr.Read(plaintext)
97+
98+
var (
99+
aead cipher.AEAD
100+
err error
101+
)
102+
switch len(nonce) {
103+
case NonceSize:
104+
aead, err = New(key[:])
105+
case NonceSizeX:
106+
aead, err = NewX(key[:])
107+
default:
108+
t.Fatalf("#%d: wrong nonce length: %d", i, len(nonce))
109+
}
110+
if err != nil {
111+
t.Fatal(err)
112+
}
88113

89-
ct := aead.Seal(nil, nonce[:], plaintext, ad)
114+
ct := aead.Seal(nil, nonce[:], plaintext, ad)
90115

91-
plaintext2, err := aead.Open(nil, nonce[:], ct, ad)
92-
if err != nil {
93-
t.Errorf("Random #%d: Open failed", i)
94-
continue
95-
}
116+
plaintext2, err := aead.Open(nil, nonce[:], ct, ad)
117+
if err != nil {
118+
t.Errorf("Random #%d: Open failed", i)
119+
continue
120+
}
96121

97-
if !bytes.Equal(plaintext, plaintext2) {
98-
t.Errorf("Random #%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext)
99-
continue
100-
}
122+
if !bytes.Equal(plaintext, plaintext2) {
123+
t.Errorf("Random #%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext)
124+
continue
125+
}
101126

102-
if len(ad) > 0 {
103-
alterAdIdx := mr.Intn(len(ad))
104-
ad[alterAdIdx] ^= 0x80
105-
if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
106-
t.Errorf("Random #%d: Open was successful after altering additional data", i)
127+
if len(ad) > 0 {
128+
alterAdIdx := mr.Intn(len(ad))
129+
ad[alterAdIdx] ^= 0x80
130+
if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
131+
t.Errorf("Random #%d: Open was successful after altering additional data", i)
132+
}
133+
ad[alterAdIdx] ^= 0x80
107134
}
108-
ad[alterAdIdx] ^= 0x80
109-
}
110135

111-
alterNonceIdx := mr.Intn(aead.NonceSize())
112-
nonce[alterNonceIdx] ^= 0x80
113-
if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
114-
t.Errorf("Random #%d: Open was successful after altering nonce", i)
115-
}
116-
nonce[alterNonceIdx] ^= 0x80
136+
alterNonceIdx := mr.Intn(aead.NonceSize())
137+
nonce[alterNonceIdx] ^= 0x80
138+
if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
139+
t.Errorf("Random #%d: Open was successful after altering nonce", i)
140+
}
141+
nonce[alterNonceIdx] ^= 0x80
117142

118-
alterCtIdx := mr.Intn(len(ct))
119-
ct[alterCtIdx] ^= 0x80
120-
if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
121-
t.Errorf("Random #%d: Open was successful after altering ciphertext", i)
143+
alterCtIdx := mr.Intn(len(ct))
144+
ct[alterCtIdx] ^= 0x80
145+
if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
146+
t.Errorf("Random #%d: Open was successful after altering ciphertext", i)
147+
}
148+
ct[alterCtIdx] ^= 0x80
122149
}
123-
ct[alterCtIdx] ^= 0x80
124150
}
151+
t.Run("Standard", func(t *testing.T) { f(t, NonceSize) })
152+
t.Run("X", func(t *testing.T) { f(t, NonceSizeX) })
125153
}
126154

127-
func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte) {
155+
func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) {
156+
b.ReportAllocs()
128157
b.SetBytes(int64(len(buf)))
129158

130159
var key [32]byte
131-
var nonce [12]byte
160+
var nonce = make([]byte, nonceSize)
132161
var ad [13]byte
133162
var out []byte
134163

135-
aead, _ := New(key[:])
164+
var aead cipher.AEAD
165+
switch len(nonce) {
166+
case NonceSize:
167+
aead, _ = New(key[:])
168+
case NonceSizeX:
169+
aead, _ = NewX(key[:])
170+
}
171+
136172
b.ResetTimer()
137173
for i := 0; i < b.N; i++ {
138174
out = aead.Seal(out[:0], nonce[:], buf[:], ad[:])
139175
}
140176
}
141177

142-
func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte) {
178+
func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
179+
b.ReportAllocs()
143180
b.SetBytes(int64(len(buf)))
144181

145182
var key [32]byte
146-
var nonce [12]byte
183+
var nonce = make([]byte, nonceSize)
147184
var ad [13]byte
148185
var ct []byte
149186
var out []byte
150187

151-
aead, _ := New(key[:])
188+
var aead cipher.AEAD
189+
switch len(nonce) {
190+
case NonceSize:
191+
aead, _ = New(key[:])
192+
case NonceSizeX:
193+
aead, _ = NewX(key[:])
194+
}
152195
ct = aead.Seal(ct[:0], nonce[:], buf[:], ad[:])
153196

154197
b.ResetTimer()
@@ -157,26 +200,20 @@ func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte) {
157200
}
158201
}
159202

160-
func BenchmarkChacha20Poly1305Open_64(b *testing.B) {
161-
benchamarkChaCha20Poly1305Open(b, make([]byte, 64))
162-
}
163-
164-
func BenchmarkChacha20Poly1305Seal_64(b *testing.B) {
165-
benchamarkChaCha20Poly1305Seal(b, make([]byte, 64))
166-
}
167-
168-
func BenchmarkChacha20Poly1305Open_1350(b *testing.B) {
169-
benchamarkChaCha20Poly1305Open(b, make([]byte, 1350))
170-
}
171-
172-
func BenchmarkChacha20Poly1305Seal_1350(b *testing.B) {
173-
benchamarkChaCha20Poly1305Seal(b, make([]byte, 1350))
174-
}
175-
176-
func BenchmarkChacha20Poly1305Open_8K(b *testing.B) {
177-
benchamarkChaCha20Poly1305Open(b, make([]byte, 8*1024))
178-
}
179-
180-
func BenchmarkChacha20Poly1305Seal_8K(b *testing.B) {
181-
benchamarkChaCha20Poly1305Seal(b, make([]byte, 8*1024))
203+
func BenchmarkChacha20Poly1305(b *testing.B) {
204+
for _, length := range []int{64, 1350, 8 * 1024} {
205+
b.Run("Open-"+strconv.Itoa(length), func(b *testing.B) {
206+
benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSize)
207+
})
208+
b.Run("Seal-"+strconv.Itoa(length), func(b *testing.B) {
209+
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize)
210+
})
211+
212+
b.Run("Open-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
213+
benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSizeX)
214+
})
215+
b.Run("Seal-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
216+
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX)
217+
})
218+
}
182219
}

chacha20poly1305/chacha20poly1305_vectors_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,4 +336,27 @@ var chacha20Poly1305Tests = []struct {
336336
"129039b5572e8a7a8131f76a",
337337
"2c125232a59879aee36cacc4aca5085a4688c4f776667a8fbd86862b5cfb1d57c976688fdd652eafa2b88b1b8e358aa2110ff6ef13cdc1ceca9c9f087c35c38d89d6fbd8de89538070f17916ecb19ca3ef4a1c834f0bdaa1df62aaabef2e117106787056c909e61ecd208357dd5c363f11c5d6cf24992cc873cf69f59360a820fcf290bd90b2cab24c47286acb4e1033962b6d41e562a206a94796a8ab1c6b8bade804ff9bdf5ba6062d2c1f8fe0f4dfc05720bd9a612b92c26789f9f6a7ce43f5e8e3aee99a9cd7d6c11eaa611983c36935b0dda57d898a60a0ab7c4b54",
338338
},
339+
340+
// XChaCha20-Poly1305 vectors
341+
{
342+
"000000000000000000000000000000",
343+
"",
344+
"0000000000000000000000000000000000000000000000000000000000000000",
345+
"000000000000000000000000000000000000000000000000",
346+
"789e9689e5208d7fd9e1f3c5b5341fb2f7033812ac9ebd3745e2c99c7bbfeb",
347+
},
348+
{
349+
"02dc819b71875e49f5e1e5a768141cfd3f14307ae61a34d81decd9a3367c00c7",
350+
"",
351+
"b7bbfe61b8041658ddc95d5cbdc01bbe7626d24f3a043b70ddee87541234cff7",
352+
"e293239d4c0a07840c5f83cb515be7fd59c333933027e99c",
353+
"7a51f271bd2e547943c7be3316c05519a5d16803712289aa2369950b1504dd8267222e47b13280077ecada7b8795d535",
354+
},
355+
{
356+
"7afc5f3f24155002e17dc176a8f1f3a097ff5a991b02ff4640f70b90db0c15c328b696d6998ea7988edfe3b960e47824e4ae002fbe589be57896a9b7bf5578599c6ba0153c7c",
357+
"d499bb9758debe59a93783c61974b7",
358+
"4ea8fab44a07f7ffc0329b2c2f8f994efdb6d505aec32113ae324def5d929ba1",
359+
"404d5086271c58bf27b0352a205d21ce4367d7b6a7628961",
360+
"26d2b46ad58b6988e2dcf1d09ba8ab6f532dc7e0847cdbc0ed00284225c02bbdb278ee8381ebd127a06926107d1b731cfb1521b267168926492e8f77219ad922257a5be2c5e52e6183ca4dfd0ad3912d7bd1ec968065",
361+
},
339362
}

0 commit comments

Comments
 (0)