chacha20_test.go raw

   1  package chacha20
   2  
   3  import (
   4  	"bytes"
   5  	"encoding/binary"
   6  	"encoding/hex"
   7  	"testing"
   8  )
   9  
  10  // rfcKey and rfcNonce are the inputs from RFC 8439, Section 2.4.2.
  11  var (
  12  	rfcKey   = [32]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}
  13  	rfcNonce = [12]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00}
  14  )
  15  
  16  // TestBlockRFC8439 checks the block function against the state matrices and
  17  // keystream published in RFC 8439, Section 2.4.2 (initial counter 1).
  18  func TestBlockRFC8439(t *testing.T) {
  19  	wantWords := [2][16]uint32{
  20  		{
  21  			0xf3514f22, 0xe1d91b40, 0x6f27de2f, 0xed1d63b8,
  22  			0x821f138c, 0xe2062c3d, 0xecca4f7e, 0x78cff39e,
  23  			0xa30a3b8a, 0x920a6072, 0xcd7479b5, 0x34932bed,
  24  			0x40ba4c79, 0xcd343ec6, 0x4c2c21ea, 0xb7417df0,
  25  		},
  26  		{
  27  			0x9f74a669, 0x410f633f, 0x28feca22, 0x7ec44dec,
  28  			0x6d34d426, 0x738cb970, 0x3ac5e9f3, 0x45590cc4,
  29  			0xda6e8b39, 0x892c831a, 0xcdea67c1, 0x2b7e1d90,
  30  			0x037463f3, 0xa11a2073, 0xe8bcfb88, 0xedc49139,
  31  		},
  32  	}
  33  
  34  	for i, counter := range []uint32{1, 2} {
  35  		got := Block(&rfcKey, counter, &rfcNonce)
  36  		for j, want := range wantWords[i] {
  37  			if v := binary.LittleEndian.Uint32(got[4*j : 4*j+4]); v != want {
  38  				t.Fatalf("counter %d word %d = %08x, want %08x", counter, j, v, want)
  39  			}
  40  		}
  41  	}
  42  
  43  	const keystreamHex = "" +
  44  		"224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf78" +
  45  		"8a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b7" +
  46  		"69a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945" +
  47  		"398b6eda1a832c89c167eacd901d7e2bf363"
  48  	wantKeystream, err := hex.DecodeString(keystreamHex)
  49  	if err != nil {
  50  		t.Fatal(err)
  51  	}
  52  	if len(wantKeystream) != 114 {
  53  		t.Fatalf("bad test vector length %d", len(wantKeystream))
  54  	}
  55  
  56  	// The RFC keystream spans two blocks starting at counter 1.
  57  	var got []byte
  58  	b1 := Block(&rfcKey, 1, &rfcNonce)
  59  	b2 := Block(&rfcKey, 2, &rfcNonce)
  60  	got = append(got, b1[:]...)
  61  	got = append(got, b2[:]...)
  62  	got = got[:len(wantKeystream)]
  63  	if !bytes.Equal(got, wantKeystream) {
  64  		t.Fatalf("keystream mismatch:\n got %x\nwant %x", got, wantKeystream)
  65  	}
  66  }
  67  
  68  // TestXORKeyStreamRFC8439 encrypts the sunscreen plaintext from RFC 8439,
  69  // Section 2.4.2 and compares against the published ciphertext.
  70  func TestXORKeyStreamRFC8439(t *testing.T) {
  71  	plaintext := []byte("Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.")
  72  	const ciphertextHex = "" +
  73  		"6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b" +
  74  		"f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8" +
  75  		"07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736" +
  76  		"5af90bbf74a35be6b40b8eedf2785e42874d"
  77  	wantCiphertext, err := hex.DecodeString(ciphertextHex)
  78  	if err != nil {
  79  		t.Fatal(err)
  80  	}
  81  
  82  	// XORKeyStream always starts at counter 0, so prepend a block's worth of
  83  	// keystream: XORing the first 64 dummy bytes consumes counter 0, and the
  84  	// RFC plaintext is then encrypted with counters 1 and 2.
  85  	dummy := make([]byte, 64)
  86  	buf := append(append([]byte{}, dummy...), plaintext...)
  87  	XORKeyStream(&rfcKey, &rfcNonce, buf, buf)
  88  
  89  	got := buf[64:]
  90  	if !bytes.Equal(got, wantCiphertext) {
  91  		t.Fatalf("ciphertext mismatch:\n got %x\nwant %x", got, wantCiphertext)
  92  	}
  93  }
  94  
  95  // TestXORKeyStreamCounterStartsAtZero verifies that the first block uses
  96  // counter 0 and that subsequent blocks increment the counter.
  97  func TestXORKeyStreamCounterStartsAtZero(t *testing.T) {
  98  	var key [32]byte
  99  	var nonce [12]byte
 100  	for i := range key {
 101  		key[i] = byte(i)
 102  	}
 103  	for i := range nonce {
 104  		nonce[i] = byte(0xa0 + i)
 105  	}
 106  	src := make([]byte, 3*BlockSize)
 107  	for i := range src {
 108  		src[i] = byte(i)
 109  	}
 110  
 111  	dst := make([]byte, len(src))
 112  	XORKeyStream(&key, &nonce, dst, src)
 113  
 114  	want := make([]byte, len(src))
 115  	for b := 0; b < 3; b++ {
 116  		ks := Block(&key, uint32(b), &nonce)
 117  		for i := 0; i < BlockSize; i++ {
 118  			want[b*BlockSize+i] = src[b*BlockSize+i] ^ ks[i]
 119  		}
 120  	}
 121  	if !bytes.Equal(dst, want) {
 122  		t.Fatal("keystream is not the counter 0,1,2 sequence")
 123  	}
 124  }
 125  
 126  // TestXORKeyStreamAlias checks in-place operation and non-overlapping output.
 127  func TestXORKeyStreamAlias(t *testing.T) {
 128  	var key [32]byte
 129  	var nonce [12]byte
 130  	for i := range key {
 131  		key[i] = byte(0x11 * i)
 132  	}
 133  	for i := range nonce {
 134  		nonce[i] = byte(0x30 + i)
 135  	}
 136  	plaintext := make([]byte, 201) // not a multiple of the block size
 137  	for i := range plaintext {
 138  		plaintext[i] = byte(7*i + 3)
 139  	}
 140  
 141  	// dst and src are the same slice.
 142  	aliased := append([]byte{}, plaintext...)
 143  	XORKeyStream(&key, &nonce, aliased, aliased)
 144  	if bytes.Equal(aliased, plaintext) {
 145  		t.Fatal("in-place XOR did not change the buffer")
 146  	}
 147  
 148  	// dst and src are distinct.
 149  	separate := make([]byte, len(plaintext))
 150  	XORKeyStream(&key, &nonce, separate, plaintext)
 151  	if !bytes.Equal(aliased, separate) {
 152  		t.Fatal("aliased and non-aliased results differ")
 153  	}
 154  	ciphertext := append([]byte{}, separate...)
 155  
 156  	// Round trip restores the plaintext in both cases.
 157  	XORKeyStream(&key, &nonce, aliased, aliased)
 158  	if !bytes.Equal(aliased, plaintext) {
 159  		t.Fatal("in-place round trip failed")
 160  	}
 161  	XORKeyStream(&key, &nonce, separate, separate)
 162  	if !bytes.Equal(separate, plaintext) {
 163  		t.Fatal("round trip failed")
 164  	}
 165  
 166  	// A dst longer than src leaves the tail untouched.
 167  	long := make([]byte, len(plaintext)+17)
 168  	for i := range long {
 169  		long[i] = 0xee
 170  	}
 171  	XORKeyStream(&key, &nonce, long, plaintext)
 172  	if !bytes.Equal(long[len(plaintext):], bytes.Repeat([]byte{0xee}, 17)) {
 173  		t.Fatal("wrote past len(src)")
 174  	}
 175  	if !bytes.Equal(long[:len(plaintext)], ciphertext) {
 176  		t.Fatal("short-copy mismatch")
 177  	}
 178  }
 179  
 180  // TestXORKeyStreamEmpty checks the zero-length case.
 181  func TestXORKeyStreamEmpty(t *testing.T) {
 182  	var key [32]byte
 183  	var nonce [12]byte
 184  	XORKeyStream(&key, &nonce, nil, nil)
 185  	XORKeyStream(&key, &nonce, []byte{}, []byte{})
 186  }
 187  
 188  func TestXORKeyStreamShortDstPanics(t *testing.T) {
 189  	var key [32]byte
 190  	var nonce [12]byte
 191  	defer func() {
 192  		if recover() == nil {
 193  			t.Fatal("expected panic for short destination")
 194  		}
 195  	}()
 196  	XORKeyStream(&key, &nonce, make([]byte, 3), make([]byte, 4))
 197  }
 198