254 lines
9.5 KiB
Python
254 lines
9.5 KiB
Python
import json
|
|
from pathlib import Path
|
|
from sys import stderr
|
|
|
|
import aegis.aegis128l
|
|
import aegis.aegis128x2
|
|
import aegis.aegis128x4
|
|
import aegis.aegis256
|
|
import aegis.aegis256x2
|
|
import aegis.aegis256x4
|
|
import pytest
|
|
|
|
from .util import random_split_bytes
|
|
|
|
|
|
def load_encryption_test_vectors():
|
|
"""Load encryption test vectors from all algorithm-specific JSON files."""
|
|
test_vectors_path = Path(__file__).parent / "test-vectors"
|
|
vectors = []
|
|
|
|
# Map filename to algorithm module
|
|
algorithm_files = {
|
|
"aegis-128l-test-vectors.json": aegis.aegis128l,
|
|
"aegis-128x2-test-vectors.json": aegis.aegis128x2,
|
|
"aegis-128x4-test-vectors.json": aegis.aegis128x4,
|
|
"aegis-256-test-vectors.json": aegis.aegis256,
|
|
"aegis-256x2-test-vectors.json": aegis.aegis256x2,
|
|
"aegis-256x4-test-vectors.json": aegis.aegis256x4,
|
|
}
|
|
|
|
for filename, alg_module in algorithm_files.items():
|
|
filepath = test_vectors_path / filename
|
|
with open(filepath, "r") as f:
|
|
file_vectors = json.load(f)
|
|
# Filter and add algorithm info to testable vectors
|
|
for vector in file_vectors:
|
|
# Only include vectors that can be tested via the Python API
|
|
if (
|
|
"key" in vector
|
|
and "nonce" in vector
|
|
and ("msg" in vector or "error" in vector)
|
|
):
|
|
vector["_algorithm"] = alg_module
|
|
vectors.append(vector)
|
|
|
|
return vectors
|
|
|
|
|
|
def get_encryption_test_id(vector):
|
|
"""Generate a test ID from the vector name and algorithm."""
|
|
alg_name = vector["_algorithm"].__name__.split(".")[
|
|
-1
|
|
] # e.g., "aegis256" -> "aegis256"
|
|
vector_name = vector.get("name", "Unknown")
|
|
return f"{alg_name}-{vector_name}"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"vector", load_encryption_test_vectors(), ids=get_encryption_test_id
|
|
)
|
|
def test_encrypt_decrypt(vector):
|
|
"""Test encryption and decryption against test vectors."""
|
|
alg = vector["_algorithm"]
|
|
|
|
key = bytes.fromhex(vector["key"])
|
|
nonce = bytes.fromhex(vector["nonce"])
|
|
ad = bytes.fromhex(vector.get("ad", ""))
|
|
|
|
if "msg" in vector:
|
|
# Test encryption/decryption with valid message
|
|
msg = bytes.fromhex(vector["msg"])
|
|
|
|
# Test 128-bit MAC if present
|
|
if "tag128" in vector:
|
|
expected_tag128 = bytes.fromhex(vector["tag128"])
|
|
ct, mac = alg.encrypt_detached(key, nonce, msg, ad, maclen=16)
|
|
|
|
# Verify MAC
|
|
assert bytes(mac) == expected_tag128, (
|
|
f"128-bit MAC mismatch for {vector['name']}"
|
|
)
|
|
|
|
# Verify ciphertext if present
|
|
if "ct" in vector:
|
|
expected_ct = bytes.fromhex(vector["ct"])
|
|
assert bytes(ct) == expected_ct, (
|
|
f"Ciphertext mismatch for {vector['name']}"
|
|
)
|
|
|
|
# Test successful decryption
|
|
decrypted = alg.decrypt_detached(key, nonce, ct, expected_tag128, ad)
|
|
assert bytes(decrypted) == msg, (
|
|
f"Decryption failed for 128-bit MAC in {vector['name']}"
|
|
)
|
|
|
|
# Test 256-bit MAC if present
|
|
if "tag256" in vector:
|
|
expected_tag256 = bytes.fromhex(vector["tag256"])
|
|
ct, mac = alg.encrypt_detached(key, nonce, msg, ad, maclen=32)
|
|
|
|
# Verify MAC
|
|
assert bytes(mac) == expected_tag256, (
|
|
f"256-bit MAC mismatch for {vector['name']}"
|
|
)
|
|
|
|
# Verify ciphertext if present
|
|
if "ct" in vector:
|
|
expected_ct = bytes.fromhex(vector["ct"])
|
|
assert bytes(ct) == expected_ct, (
|
|
f"Ciphertext mismatch for {vector['name']}"
|
|
)
|
|
|
|
# Test successful decryption
|
|
decrypted = alg.decrypt_detached(key, nonce, ct, expected_tag256, ad)
|
|
assert bytes(decrypted) == msg, (
|
|
f"Decryption failed for 256-bit MAC in {vector['name']}"
|
|
)
|
|
|
|
elif "error" in vector:
|
|
# Test decryption failure cases
|
|
ct = bytes.fromhex(vector["ct"])
|
|
|
|
# Test that decryption fails with the provided (invalid) MACs
|
|
if "tag128" in vector:
|
|
invalid_mac = bytes.fromhex(vector["tag128"])
|
|
with pytest.raises(ValueError, match="authentication failed"):
|
|
alg.decrypt_detached(key, nonce, ct, invalid_mac, ad)
|
|
|
|
if "tag256" in vector:
|
|
invalid_mac = bytes.fromhex(vector["tag256"])
|
|
with pytest.raises(ValueError, match="authentication failed"):
|
|
alg.decrypt_detached(key, nonce, ct, invalid_mac, ad)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"vector", load_encryption_test_vectors(), ids=get_encryption_test_id
|
|
)
|
|
def test_encrypt_decrypt_incremental(vector):
|
|
"""Test incremental encryption and decryption using Encryptor/Decryptor classes."""
|
|
alg = vector["_algorithm"]
|
|
|
|
key = bytes.fromhex(vector["key"])
|
|
nonce = bytes.fromhex(vector["nonce"])
|
|
ad = bytes.fromhex(vector.get("ad", ""))
|
|
|
|
if "msg" in vector:
|
|
# Test incremental encryption/decryption with valid message
|
|
msg = bytes.fromhex(vector["msg"])
|
|
|
|
# Test 128-bit MAC if present
|
|
if "tag128" in vector:
|
|
expected_tag128 = bytes.fromhex(vector["tag128"])
|
|
|
|
# Incremental encryption with random chunking
|
|
encryptor = alg.Encryptor(key, nonce, ad)
|
|
ct_chunks = []
|
|
for chunk in random_split_bytes(msg):
|
|
ct_result = encryptor.update(chunk)
|
|
ct_chunks.append(bytes(ct_result))
|
|
final_output = encryptor.final(maclen=16)
|
|
ct_chunks.append(bytes(final_output[:-16])) # ciphertext part
|
|
computed_mac = bytes(final_output[-16:]) # MAC part
|
|
|
|
# Combine ciphertext chunks
|
|
computed_ct = b"".join(ct_chunks)
|
|
|
|
# Verify against expected values
|
|
assert bytes(computed_mac) == expected_tag128, (
|
|
f"128-bit MAC mismatch for {vector['name']}"
|
|
)
|
|
if "ct" in vector:
|
|
expected_ct = bytes.fromhex(vector["ct"])
|
|
assert computed_ct == expected_ct, (
|
|
f"Ciphertext mismatch for {vector['name']}"
|
|
)
|
|
|
|
# Incremental decryption with different random chunking
|
|
decryptor = alg.Decryptor(key, nonce, ad)
|
|
pt_chunks = []
|
|
stderr.write(f"CT len={len(computed_ct)}\n")
|
|
for chunk in random_split_bytes(computed_ct):
|
|
stderr.write(
|
|
f"Processing chunk of size {len(chunk)} {decryptor.bytes_in=}, out {alg.calc_update_output_size(decryptor.bytes_in, len(chunk))}\n"
|
|
)
|
|
pt_chunks.append(bytes(decryptor.update(chunk)))
|
|
final_pt = decryptor.final(expected_tag128)
|
|
pt_chunks.append(bytes(final_pt))
|
|
|
|
# Combine plaintext chunks
|
|
computed_pt = b"".join(pt_chunks)
|
|
assert computed_pt == msg, (
|
|
f"Decryption failed for 128-bit MAC in {vector['name']}"
|
|
)
|
|
|
|
# Test 256-bit MAC if present
|
|
if "tag256" in vector:
|
|
expected_tag256 = bytes.fromhex(vector["tag256"])
|
|
|
|
# Incremental encryption with random chunking
|
|
encryptor = alg.Encryptor(key, nonce, ad)
|
|
ct_chunks = []
|
|
for chunk in random_split_bytes(msg):
|
|
ct_result = encryptor.update(chunk)
|
|
ct_chunks.append(bytes(ct_result))
|
|
final_output = encryptor.final(maclen=32)
|
|
ct_chunks.append(bytes(final_output[:-32])) # ciphertext part
|
|
computed_mac = bytes(final_output[-32:]) # MAC part
|
|
|
|
# Combine ciphertext chunks
|
|
computed_ct = b"".join(ct_chunks)
|
|
|
|
# Verify against expected values
|
|
assert bytes(computed_mac) == expected_tag256, (
|
|
f"256-bit MAC mismatch for {vector['name']}"
|
|
)
|
|
if "ct" in vector:
|
|
expected_ct = bytes.fromhex(vector["ct"])
|
|
assert computed_ct == expected_ct, (
|
|
f"Ciphertext mismatch for {vector['name']}"
|
|
)
|
|
|
|
# Incremental decryption with different random chunking
|
|
decryptor = alg.Decryptor(key, nonce, ad)
|
|
pt_chunks = []
|
|
for chunk in random_split_bytes(computed_ct):
|
|
pt_chunks.append(bytes(decryptor.update(chunk)))
|
|
final_pt = decryptor.final(expected_tag256)
|
|
pt_chunks.append(bytes(final_pt))
|
|
|
|
# Combine plaintext chunks
|
|
computed_pt = b"".join(pt_chunks)
|
|
assert computed_pt == msg, (
|
|
f"Decryption failed for 256-bit MAC in {vector['name']}"
|
|
)
|
|
|
|
elif "error" in vector:
|
|
# Test decryption failure cases with incremental API
|
|
ct = bytes.fromhex(vector["ct"])
|
|
|
|
# Test that incremental decryption fails with the provided (invalid) MACs
|
|
if "tag128" in vector:
|
|
invalid_mac = bytes.fromhex(vector["tag128"])
|
|
decryptor = alg.Decryptor(key, nonce, ad)
|
|
decryptor.update(ct) # This should succeed
|
|
with pytest.raises(ValueError, match="authentication failed"):
|
|
decryptor.final(invalid_mac)
|
|
|
|
if "tag256" in vector:
|
|
invalid_mac = bytes.fromhex(vector["tag256"])
|
|
decryptor = alg.Decryptor(key, nonce, ad)
|
|
decryptor.update(ct) # This should succeed
|
|
with pytest.raises(ValueError, match="authentication failed"):
|
|
decryptor.final(invalid_mac)
|