diff --git a/cipher_test.go b/cipher_test.go new file mode 100644 index 0000000..b0152a7 --- /dev/null +++ b/cipher_test.go @@ -0,0 +1,136 @@ +// cipher_test.go +package fastchacha20 + +import ( + "bytes" + "crypto/rand" + "testing" +) + +func generateRandomKey(t *testing.T, size int) []byte { + key := make([]byte, size) + if _, err := rand.Read(key); err != nil { + t.Fatalf("Failed to generate random key: %v", err) + } + return key +} + +func generateRandomNonce(t *testing.T, size int) []byte { + nonce := make([]byte, size) + if _, err := rand.Read(nonce); err != nil { + t.Fatalf("Failed to generate random nonce: %v", err) + } + return nonce +} + +func TestEncryptDecrypt(t *testing.T) { + // Generate random key and nonce + key := generateRandomKey(t, 32) // 256-bit key + nonce := generateRandomNonce(t, 24) // 192-bit nonce (for ChaCha20-Poly1305) + + cipher, err := NewCipher(key) + if err != nil { + t.Fatalf("Failed to create cipher: %v", err) + } + + plaintext := []byte("This is a test plaintext message.") + aad := []byte("Additional data for authentication") + + // Encrypt the plaintext + ciphertext, err := cipher.Encrypt(nonce, plaintext, aad) + if err != nil { + t.Fatalf("Encryption failed: %v", err) + } + + // Decrypt the ciphertext + decryptedPlaintext, err := cipher.Decrypt(nonce, ciphertext, aad) + if err != nil { + t.Fatalf("Decryption failed: %v", err) + } + + // Verify that the decrypted plaintext matches the original plaintext + if !bytes.Equal(plaintext, decryptedPlaintext) { + t.Errorf("Decrypted plaintext does not match the original.\nOriginal: %s\nDecrypted: %s", plaintext, decryptedPlaintext) + } +} + +func TestEncryptDecryptWithEmptyPlaintext(t *testing.T) { + // Generate random key and nonce + key := generateRandomKey(t, 32) + nonce := generateRandomNonce(t, 24) + + cipher, err := NewCipher(key) + if err != nil { + t.Fatalf("Failed to create cipher: %v", err) + } + + plaintext := []byte("") // Empty plaintext + aad := []byte("Additional data") + + // Encrypt the empty plaintext + ciphertext, err := cipher.Encrypt(nonce, plaintext, aad) + if err != nil { + t.Fatalf("Encryption failed: %v", err) + } + + // Decrypt the ciphertext + decryptedPlaintext, err := cipher.Decrypt(nonce, ciphertext, aad) + if err != nil { + t.Fatalf("Decryption failed: %v", err) + } + + // Verify that the decrypted plaintext is also empty + if len(decryptedPlaintext) != 0 { + t.Errorf("Expected empty decrypted plaintext, got length %d", len(decryptedPlaintext)) + } +} + +func TestInvalidNonceLength(t *testing.T) { + // Generate a valid key + key := generateRandomKey(t, 32) + + cipher, err := NewCipher(key) + if err != nil { + t.Fatalf("Failed to create cipher: %v", err) + } + + // Generate invalid nonce + invalidNonce := generateRandomNonce(t, 16) // Should be 24 bytes for ChaCha20-Poly1305 + plaintext := []byte("Test plaintext") + aad := []byte("AAD data") + + // Try to encrypt with the invalid nonce + _, err = cipher.Encrypt(invalidNonce, plaintext, aad) + if err == nil { + t.Errorf("Expected error due to invalid nonce length, but encryption succeeded") + } +} + +func TestDecryptWithInvalidNonce(t *testing.T) { + // Generate random key and nonce + key := generateRandomKey(t, 32) + nonce := generateRandomNonce(t, 24) + + cipher, err := NewCipher(key) + if err != nil { + t.Fatalf("Failed to create cipher: %v", err) + } + + plaintext := []byte("Valid plaintext message.") + aad := []byte("Authenticated data") + + // Encrypt the plaintext + ciphertext, err := cipher.Encrypt(nonce, plaintext, aad) + if err != nil { + t.Fatalf("Encryption failed: %v", err) + } + + // Modify the nonce (introduce an error) + invalidNonce := generateRandomNonce(t, 24) + + // Attempt to decrypt with an invalid nonce + _, err = cipher.Decrypt(invalidNonce, ciphertext, aad) + if err == nil { + t.Error("Decryption should have failed due to invalid nonce, but it succeeded") + } +} diff --git a/parallel.go b/parallel.go index 9286536..29eca00 100644 --- a/parallel.go +++ b/parallel.go @@ -1,11 +1,10 @@ -// fastchacha20_parallel.go +// parallel.go package fastchacha20 import ( "bytes" "crypto/rand" "encoding/binary" - "errors" "sync" ) @@ -40,9 +39,16 @@ func (c *Cipher) EncryptChunks(plaintext []byte) ([][]byte, error) { aad := make([]byte, 8) binary.BigEndian.PutUint64(aad, uint64(i)) + // Encrypt the chunk using the Encrypt method from cipher.go + ciphertext, e := c.Encrypt(nonce, chunk, aad) + if e != nil { + errOnce.Do(func() { err = e }) + return + } + // Prepend nonce to the ciphertext for storage - ciphertext := c.aead.Seal(nonce, nonce, chunk, aad) - encryptedChunks[i] = ciphertext + encryptedChunk := append(nonce, ciphertext...) + encryptedChunks[i] = encryptedChunk }(i) } wg.Wait() @@ -68,7 +74,7 @@ func (c *Cipher) DecryptChunks(encryptedChunks [][]byte) ([]byte, error) { defer wg.Done() nonceSize := c.aead.NonceSize() if len(chunk) < nonceSize+16 { // 16 bytes for Poly1305 tag - errOnce.Do(func() { err = errors.New("ciphertext too short") }) + errOnce.Do(func() { err = ErrInvalidCiphertext }) return } @@ -79,7 +85,8 @@ func (c *Cipher) DecryptChunks(encryptedChunks [][]byte) ([]byte, error) { aad := make([]byte, 8) binary.BigEndian.PutUint64(aad, uint64(i)) - plaintext, e := c.aead.Open(nil, nonce, ciphertext, aad) + // Decrypt the chunk using the Decrypt method from cipher.go + plaintext, e := c.Decrypt(nonce, ciphertext, aad) if e != nil { errOnce.Do(func() { err = e }) return