The example was failing to direct users on what to do with the nonce, which should be almost universally prepended to the ciphertext. Also, leaving key out of the function was pulling the entire file into the example. Updates golang/go#38369 Change-Id: I3a13342fff02e1a5d6f9c4ef2a8bddf0b1097707 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/227937 Run-TryBot: Filippo Valsorda <filippo@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Katie Hockman <katie@golang.org>
269 lines
6.6 KiB
Go
269 lines
6.6 KiB
Go
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package chacha20poly1305
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/cipher"
|
|
cryptorand "crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
mathrand "math/rand"
|
|
"strconv"
|
|
"testing"
|
|
)
|
|
|
|
func TestVectors(t *testing.T) {
|
|
for i, test := range chacha20Poly1305Tests {
|
|
key, _ := hex.DecodeString(test.key)
|
|
nonce, _ := hex.DecodeString(test.nonce)
|
|
ad, _ := hex.DecodeString(test.aad)
|
|
plaintext, _ := hex.DecodeString(test.plaintext)
|
|
|
|
var (
|
|
aead cipher.AEAD
|
|
err error
|
|
)
|
|
switch len(nonce) {
|
|
case NonceSize:
|
|
aead, err = New(key)
|
|
case NonceSizeX:
|
|
aead, err = NewX(key)
|
|
default:
|
|
t.Fatalf("#%d: wrong nonce length: %d", i, len(nonce))
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ct := aead.Seal(nil, nonce, plaintext, ad)
|
|
if ctHex := hex.EncodeToString(ct); ctHex != test.out {
|
|
t.Errorf("#%d: got %s, want %s", i, ctHex, test.out)
|
|
continue
|
|
}
|
|
|
|
plaintext2, err := aead.Open(nil, nonce, ct, ad)
|
|
if err != nil {
|
|
t.Errorf("#%d: Open failed", i)
|
|
continue
|
|
}
|
|
|
|
if !bytes.Equal(plaintext, plaintext2) {
|
|
t.Errorf("#%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext)
|
|
continue
|
|
}
|
|
|
|
if len(ad) > 0 {
|
|
alterAdIdx := mathrand.Intn(len(ad))
|
|
ad[alterAdIdx] ^= 0x80
|
|
if _, err := aead.Open(nil, nonce, ct, ad); err == nil {
|
|
t.Errorf("#%d: Open was successful after altering additional data", i)
|
|
}
|
|
ad[alterAdIdx] ^= 0x80
|
|
}
|
|
|
|
alterNonceIdx := mathrand.Intn(aead.NonceSize())
|
|
nonce[alterNonceIdx] ^= 0x80
|
|
if _, err := aead.Open(nil, nonce, ct, ad); err == nil {
|
|
t.Errorf("#%d: Open was successful after altering nonce", i)
|
|
}
|
|
nonce[alterNonceIdx] ^= 0x80
|
|
|
|
alterCtIdx := mathrand.Intn(len(ct))
|
|
ct[alterCtIdx] ^= 0x80
|
|
if _, err := aead.Open(nil, nonce, ct, ad); err == nil {
|
|
t.Errorf("#%d: Open was successful after altering ciphertext", i)
|
|
}
|
|
ct[alterCtIdx] ^= 0x80
|
|
}
|
|
}
|
|
|
|
func TestRandom(t *testing.T) {
|
|
// Some random tests to verify Open(Seal) == Plaintext
|
|
f := func(t *testing.T, nonceSize int) {
|
|
for i := 0; i < 256; i++ {
|
|
var nonce = make([]byte, nonceSize)
|
|
var key [32]byte
|
|
|
|
al := mathrand.Intn(128)
|
|
pl := mathrand.Intn(16384)
|
|
ad := make([]byte, al)
|
|
plaintext := make([]byte, pl)
|
|
cryptorand.Read(key[:])
|
|
cryptorand.Read(nonce[:])
|
|
cryptorand.Read(ad)
|
|
cryptorand.Read(plaintext)
|
|
|
|
var (
|
|
aead cipher.AEAD
|
|
err error
|
|
)
|
|
switch len(nonce) {
|
|
case NonceSize:
|
|
aead, err = New(key[:])
|
|
case NonceSizeX:
|
|
aead, err = NewX(key[:])
|
|
default:
|
|
t.Fatalf("#%d: wrong nonce length: %d", i, len(nonce))
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ct := aead.Seal(nil, nonce[:], plaintext, ad)
|
|
|
|
plaintext2, err := aead.Open(nil, nonce[:], ct, ad)
|
|
if err != nil {
|
|
t.Errorf("Random #%d: Open failed", i)
|
|
continue
|
|
}
|
|
|
|
if !bytes.Equal(plaintext, plaintext2) {
|
|
t.Errorf("Random #%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext)
|
|
continue
|
|
}
|
|
|
|
if len(ad) > 0 {
|
|
alterAdIdx := mathrand.Intn(len(ad))
|
|
ad[alterAdIdx] ^= 0x80
|
|
if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
|
|
t.Errorf("Random #%d: Open was successful after altering additional data", i)
|
|
}
|
|
ad[alterAdIdx] ^= 0x80
|
|
}
|
|
|
|
alterNonceIdx := mathrand.Intn(aead.NonceSize())
|
|
nonce[alterNonceIdx] ^= 0x80
|
|
if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
|
|
t.Errorf("Random #%d: Open was successful after altering nonce", i)
|
|
}
|
|
nonce[alterNonceIdx] ^= 0x80
|
|
|
|
alterCtIdx := mathrand.Intn(len(ct))
|
|
ct[alterCtIdx] ^= 0x80
|
|
if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
|
|
t.Errorf("Random #%d: Open was successful after altering ciphertext", i)
|
|
}
|
|
ct[alterCtIdx] ^= 0x80
|
|
}
|
|
}
|
|
t.Run("Standard", func(t *testing.T) { f(t, NonceSize) })
|
|
t.Run("X", func(t *testing.T) { f(t, NonceSizeX) })
|
|
}
|
|
|
|
func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) {
|
|
b.ReportAllocs()
|
|
b.SetBytes(int64(len(buf)))
|
|
|
|
var key [32]byte
|
|
var nonce = make([]byte, nonceSize)
|
|
var ad [13]byte
|
|
var out []byte
|
|
|
|
var aead cipher.AEAD
|
|
switch len(nonce) {
|
|
case NonceSize:
|
|
aead, _ = New(key[:])
|
|
case NonceSizeX:
|
|
aead, _ = NewX(key[:])
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
out = aead.Seal(out[:0], nonce[:], buf[:], ad[:])
|
|
}
|
|
}
|
|
|
|
func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
|
|
b.ReportAllocs()
|
|
b.SetBytes(int64(len(buf)))
|
|
|
|
var key [32]byte
|
|
var nonce = make([]byte, nonceSize)
|
|
var ad [13]byte
|
|
var ct []byte
|
|
var out []byte
|
|
|
|
var aead cipher.AEAD
|
|
switch len(nonce) {
|
|
case NonceSize:
|
|
aead, _ = New(key[:])
|
|
case NonceSizeX:
|
|
aead, _ = NewX(key[:])
|
|
}
|
|
ct = aead.Seal(ct[:0], nonce[:], buf[:], ad[:])
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
out, _ = aead.Open(out[:0], nonce[:], ct[:], ad[:])
|
|
}
|
|
}
|
|
|
|
func BenchmarkChacha20Poly1305(b *testing.B) {
|
|
for _, length := range []int{64, 1350, 8 * 1024} {
|
|
b.Run("Open-"+strconv.Itoa(length), func(b *testing.B) {
|
|
benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSize)
|
|
})
|
|
b.Run("Seal-"+strconv.Itoa(length), func(b *testing.B) {
|
|
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize)
|
|
})
|
|
|
|
b.Run("Open-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
|
|
benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSizeX)
|
|
})
|
|
b.Run("Seal-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
|
|
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX)
|
|
})
|
|
}
|
|
}
|
|
|
|
func ExampleNewX() {
|
|
// key should be randomly generated or derived from a function like Argon2.
|
|
key := make([]byte, KeySize)
|
|
if _, err := cryptorand.Read(key); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
aead, err := NewX(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Encryption.
|
|
var encryptedMsg []byte
|
|
{
|
|
msg := []byte("Gophers, gophers, gophers everywhere!")
|
|
|
|
// Select a random nonce, and leave capacity for the ciphertext.
|
|
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead())
|
|
if _, err := cryptorand.Read(nonce); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Encrypt the message and append the ciphertext to the nonce.
|
|
encryptedMsg = aead.Seal(nonce, nonce, msg, nil)
|
|
}
|
|
|
|
// Decryption.
|
|
{
|
|
if len(encryptedMsg) < aead.NonceSize() {
|
|
panic("ciphertext too short")
|
|
}
|
|
|
|
// Split nonce and ciphertext.
|
|
nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]
|
|
|
|
// Decrypt the message and check it wasn't tampered with.
|
|
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Printf("%s\n", plaintext)
|
|
}
|
|
|
|
// Output: Gophers, gophers, gophers everywhere!
|
|
}
|