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!
|
|
}
|