ctr_test.go raw

   1  package crypto
   2  
   3  import (
   4  	"bytes"
   5  	"testing"
   6  )
   7  
   8  func TestCTRFromSecretRoundTrip(t *testing.T) {
   9  	secret := Hash([]byte("test-secret-key"))
  10  	nonce := []byte("test-nonce-001")
  11  	plaintext := []byte("Hello, hamadryad CTR encryption!")
  12  
  13  	stream := NewCTRStreamFromSecret(secret, nonce)
  14  	ciphertext := stream.Encrypt(plaintext)
  15  
  16  	if len(ciphertext) != len(plaintext) {
  17  		t.Errorf("ciphertext length = %d, want %d", len(ciphertext), len(plaintext))
  18  	}
  19  	if bytes.Equal(ciphertext, plaintext) {
  20  		t.Error("ciphertext should differ from plaintext")
  21  	}
  22  
  23  	stream2 := NewCTRStreamFromSecret(secret, nonce)
  24  	recovered := stream2.Decrypt(ciphertext)
  25  	if !bytes.Equal(recovered, plaintext) {
  26  		t.Errorf("recovered = %q, want %q", recovered, plaintext)
  27  	}
  28  }
  29  
  30  func TestCTRDeterministic(t *testing.T) {
  31  	secret := Hash([]byte("determinism-key"))
  32  	nonce := []byte("determinism-nonce")
  33  	plaintext := []byte("same message twice")
  34  
  35  	s1 := NewCTRStreamFromSecret(secret, nonce)
  36  	s2 := NewCTRStreamFromSecret(secret, nonce)
  37  
  38  	ct1 := s1.Encrypt(plaintext)
  39  	ct2 := s2.Encrypt(plaintext)
  40  	if !bytes.Equal(ct1, ct2) {
  41  		t.Error("CTR encryption should be deterministic with same key+nonce")
  42  	}
  43  }
  44  
  45  func TestCTRDifferentNonces(t *testing.T) {
  46  	secret := Hash([]byte("nonce-test-key"))
  47  	plaintext := []byte("same plaintext")
  48  
  49  	s1 := NewCTRStreamFromSecret(secret, []byte("nonce-1"))
  50  	s2 := NewCTRStreamFromSecret(secret, []byte("nonce-2"))
  51  
  52  	ct1 := s1.Encrypt(plaintext)
  53  	ct2 := s2.Encrypt(plaintext)
  54  	if bytes.Equal(ct1, ct2) {
  55  		t.Error("different nonces should produce different ciphertext")
  56  	}
  57  }
  58  
  59  func TestCTRDifferentSecrets(t *testing.T) {
  60  	secret1 := Hash([]byte("secret-1"))
  61  	secret2 := Hash([]byte("secret-2"))
  62  	nonce := []byte("same-nonce")
  63  	plaintext := []byte("same plaintext")
  64  
  65  	s1 := NewCTRStreamFromSecret(secret1, nonce)
  66  	s2 := NewCTRStreamFromSecret(secret2, nonce)
  67  
  68  	ct1 := s1.Encrypt(plaintext)
  69  	ct2 := s2.Encrypt(plaintext)
  70  	if bytes.Equal(ct1, ct2) {
  71  		t.Error("different secrets should produce different ciphertext")
  72  	}
  73  
  74  	// Cross-key decryption should fail.
  75  	s3 := NewCTRStreamFromSecret(secret2, nonce)
  76  	wrong := s3.Decrypt(ct1)
  77  	if bytes.Equal(wrong, plaintext) {
  78  		t.Error("wrong secret should not decrypt correctly")
  79  	}
  80  }
  81  
  82  func TestCTRRandomAccess(t *testing.T) {
  83  	secret := Hash([]byte("random-access-key"))
  84  	stream := NewCTRStreamFromSecret(secret, []byte("random-access"))
  85  
  86  	block0 := stream.KeystreamBlock(0)
  87  	block1 := stream.KeystreamBlock(1)
  88  	block2 := stream.KeystreamBlock(2)
  89  
  90  	// Order-independent.
  91  	block2r := stream.KeystreamBlock(2)
  92  	block1r := stream.KeystreamBlock(1)
  93  	block0r := stream.KeystreamBlock(0)
  94  
  95  	if !bytes.Equal(block0, block0r) {
  96  		t.Error("block 0 should be same regardless of generation order")
  97  	}
  98  	if !bytes.Equal(block1, block1r) {
  99  		t.Error("block 1 should be same regardless of generation order")
 100  	}
 101  	if !bytes.Equal(block2, block2r) {
 102  		t.Error("block 2 should be same regardless of generation order")
 103  	}
 104  	if bytes.Equal(block0, block1) {
 105  		t.Error("block 0 and 1 should differ")
 106  	}
 107  	if bytes.Equal(block1, block2) {
 108  		t.Error("block 1 and 2 should differ")
 109  	}
 110  }
 111  
 112  func TestCTRRandomAccessEncrypt(t *testing.T) {
 113  	secret := Hash([]byte("seek-test-key"))
 114  	nonce := []byte("seek-test")
 115  
 116  	plaintext := make([]byte, BlockSize*3)
 117  	for i := range plaintext {
 118  		plaintext[i] = byte(i % 256)
 119  	}
 120  
 121  	stream := NewCTRStreamFromSecret(secret, nonce)
 122  	ciphertext := stream.Encrypt(plaintext)
 123  
 124  	stream2 := NewCTRStreamFromSecret(secret, nonce)
 125  	blockSlice := ciphertext[BlockSize : BlockSize*2]
 126  	decrypted := stream2.EncryptCTRAt(blockSlice, uint64(BlockSize))
 127  
 128  	expected := plaintext[BlockSize : BlockSize*2]
 129  	if !bytes.Equal(decrypted, expected) {
 130  		t.Error("random-access decryption should recover correct block")
 131  	}
 132  }
 133  
 134  func TestCTREmptyPlaintext(t *testing.T) {
 135  	secret := Hash([]byte("empty-test"))
 136  	stream := NewCTRStreamFromSecret(secret, []byte("nonce"))
 137  	ct := stream.Encrypt([]byte{})
 138  	if len(ct) != 0 {
 139  		t.Errorf("ciphertext of empty plaintext should be empty, got %d bytes", len(ct))
 140  	}
 141  }
 142  
 143  func TestCTRBlockSizeIs64(t *testing.T) {
 144  	if BlockSize != 64 {
 145  		t.Errorf("BlockSize = %d, want 64", BlockSize)
 146  	}
 147  }
 148  
 149  func TestCTRKeystreamNonZero(t *testing.T) {
 150  	secret := Hash([]byte("nonzero-key"))
 151  	stream := NewCTRStreamFromSecret(secret, []byte("nonce"))
 152  	block := stream.KeystreamBlock(0)
 153  
 154  	allZero := true
 155  	for _, b := range block {
 156  		if b != 0 {
 157  			allZero = false
 158  			break
 159  		}
 160  	}
 161  	if allZero {
 162  		t.Error("keystream block should not be all zeros")
 163  	}
 164  }
 165