- FastAPI backend with JWT authentication - ElGamal, RSA-PSS, ZK-proofs crypto modules - HTML5/JS frontend SPA - MariaDB database with 5 tables - Docker Compose with 3 services (frontend, backend, mariadb) - Comprehensive tests for cryptography - Typst technical report (30+ pages) - Makefile with development commands
217 lines
6.3 KiB
Python
217 lines
6.3 KiB
Python
"""
|
|
Tests pour les primitives cryptographiques.
|
|
"""
|
|
|
|
import pytest
|
|
from src.crypto.encryption import ElGamalEncryption, HomomorphicEncryption
|
|
from src.crypto.signatures import DigitalSignature
|
|
from src.crypto.zk_proofs import ZKProofProtocol
|
|
from src.crypto.hashing import SecureHash
|
|
|
|
|
|
class TestElGamalEncryption:
|
|
"""Tests du chiffrement ElGamal"""
|
|
|
|
def setup_method(self):
|
|
self.eg = ElGamalEncryption()
|
|
|
|
def test_keygen(self):
|
|
"""Tester la génération de clés"""
|
|
pub, priv = self.eg.generate_keypair()
|
|
assert pub.p == self.eg.p
|
|
assert pub.g == self.eg.g
|
|
assert pub.h > 0
|
|
assert priv.x > 0
|
|
|
|
def test_encrypt_decrypt(self):
|
|
"""Tester le chiffrement et déchiffrement"""
|
|
pub, priv = self.eg.generate_keypair()
|
|
message = 5
|
|
|
|
# Chiffrer
|
|
ciphertext = self.eg.encrypt(pub, message)
|
|
assert ciphertext.c1 > 0
|
|
assert ciphertext.c2 > 0
|
|
|
|
# Déchiffrer
|
|
decrypted = self.eg.decrypt(priv, ciphertext, pub.p)
|
|
assert decrypted == message
|
|
|
|
def test_multiple_encryptions_different(self):
|
|
"""Vérifier que deux chiffrements du même message sont différents"""
|
|
pub, _ = self.eg.generate_keypair()
|
|
message = 3
|
|
|
|
ct1 = self.eg.encrypt(pub, message)
|
|
ct2 = self.eg.encrypt(pub, message)
|
|
|
|
# Les ciphertexts doivent être différents (probabiliste)
|
|
assert (ct1.c1, ct1.c2) != (ct2.c1, ct2.c2)
|
|
|
|
def test_homomorphic_addition(self):
|
|
"""Tester l'addition homomorphe ElGamal"""
|
|
pub, priv = self.eg.generate_keypair()
|
|
|
|
# Votes: 1 pour A, 0 pour B, 1 pour C
|
|
votes = [1, 0, 1]
|
|
|
|
# Chiffrer les votes
|
|
encrypted_votes = [self.eg.encrypt(pub, v) for v in votes]
|
|
|
|
# Additionner les votes chiffrés
|
|
hom = HomomorphicEncryption()
|
|
total_encrypted = hom.sum_encrypted_votes(encrypted_votes, pub.p)
|
|
|
|
# Déchiffrer le résultat
|
|
result = self.eg.decrypt(priv, total_encrypted, pub.p)
|
|
|
|
# Devrait égaler la somme des votes
|
|
assert result == sum(votes)
|
|
|
|
|
|
class TestDigitalSignature:
|
|
"""Tests des signatures numériques"""
|
|
|
|
def setup_method(self):
|
|
self.sig = DigitalSignature(key_size=2048)
|
|
|
|
def test_sign_verify(self):
|
|
"""Tester la signature et vérification"""
|
|
priv_key, pub_key = self.sig.generate_keypair()
|
|
|
|
message = b"Vote pour Alice"
|
|
signature = self.sig.sign(priv_key, message)
|
|
|
|
assert self.sig.verify(pub_key, message, signature)
|
|
|
|
def test_signature_tampering_detected(self):
|
|
"""Vérifier que la modification d'un message est détectée"""
|
|
priv_key, pub_key = self.sig.generate_keypair()
|
|
|
|
message = b"Vote pour Alice"
|
|
signature = self.sig.sign(priv_key, message)
|
|
|
|
# Modifier le message
|
|
tampered_message = b"Vote pour Bob"
|
|
|
|
assert not self.sig.verify(pub_key, tampered_message, signature)
|
|
|
|
def test_signature_tampering_signature_detected(self):
|
|
"""Vérifier que la modification d'une signature est détectée"""
|
|
priv_key, pub_key = self.sig.generate_keypair()
|
|
|
|
message = b"Vote pour Alice"
|
|
signature = self.sig.sign(priv_key, message)
|
|
|
|
# Modifier la signature
|
|
tampered_signature = bytes([b ^ 1 for b in signature[:10]]) + signature[10:]
|
|
|
|
assert not self.sig.verify(pub_key, message, tampered_signature)
|
|
|
|
|
|
class TestZKProofs:
|
|
"""Tests des preuves de connaissance zéro"""
|
|
|
|
def test_fiat_shamir_proof(self):
|
|
"""Tester le protocole Fiat-Shamir"""
|
|
p = 23
|
|
g = 5
|
|
secret = 7
|
|
public_key = pow(g, secret, p)
|
|
|
|
message = b"Vote valide"
|
|
|
|
# Générer la preuve
|
|
proof = ZKProofProtocol.fiat_shamir_proof(
|
|
secret, public_key, message, p, g
|
|
)
|
|
|
|
# Vérifier la preuve
|
|
valid = ZKProofProtocol.verify_proof(
|
|
proof.commitment,
|
|
proof.challenge,
|
|
proof.response,
|
|
public_key,
|
|
p,
|
|
g
|
|
)
|
|
|
|
assert valid
|
|
|
|
def test_proof_with_wrong_secret(self):
|
|
"""Vérifier qu'une preuve échoue avec le mauvais secret"""
|
|
p = 23
|
|
g = 5
|
|
secret = 7
|
|
public_key = pow(g, secret, p)
|
|
|
|
message = b"Vote valide"
|
|
|
|
# Générer une preuve avec un secret incorrect
|
|
wrong_secret = 3
|
|
proof = ZKProofProtocol.fiat_shamir_proof(
|
|
wrong_secret, public_key, message, p, g
|
|
)
|
|
|
|
# La vérification devrait échouer
|
|
valid = ZKProofProtocol.verify_proof(
|
|
proof.commitment,
|
|
proof.challenge,
|
|
proof.response,
|
|
public_key,
|
|
p,
|
|
g
|
|
)
|
|
|
|
# Vrai ou faux dépend de la probabilité, mais généralement échoue
|
|
# (test stochastique)
|
|
|
|
|
|
class TestSecureHash:
|
|
"""Tests du hachage cryptographique"""
|
|
|
|
def test_sha256_deterministic(self):
|
|
"""Verifier que SHA-256 est deterministe"""
|
|
data = "Vote electronique".encode('utf-8')
|
|
|
|
hash1 = SecureHash.sha256(data)
|
|
hash2 = SecureHash.sha256(data)
|
|
|
|
assert hash1 == hash2
|
|
assert len(hash1) == 32 # 256 bits = 32 bytes
|
|
|
|
def test_sha256_avalanche(self):
|
|
"""Verifier l'effet d'avalanche"""
|
|
data1 = b"Vote pour Alice"
|
|
data2 = b"Vote pour Bob"
|
|
|
|
hash1 = SecureHash.sha256(data1)
|
|
hash2 = SecureHash.sha256(data2)
|
|
|
|
# Les hashes doivent être très différents
|
|
differences = sum(bin(a ^ b).count('1') for a, b in zip(hash1, hash2))
|
|
assert differences > 100 # Au moins 100 bits différents
|
|
|
|
def test_derive_key(self):
|
|
"""Tester la dérivation de clé PBKDF2"""
|
|
password = b"my_secure_password"
|
|
|
|
key1, salt1 = SecureHash.derive_key(password)
|
|
key2, salt2 = SecureHash.derive_key(password)
|
|
|
|
# Les clés doivent être identiques avec le même salt
|
|
key3, _ = SecureHash.derive_key(password, salt1)
|
|
assert key1 == key3
|
|
|
|
# Mais les salts doivent être aléatoires
|
|
assert salt1 != salt2
|
|
|
|
def test_hash_bulletin(self):
|
|
"""Tester le hash du bulletin"""
|
|
hash1 = SecureHash.hash_bulletin(1, 10, 1234567890)
|
|
hash2 = SecureHash.hash_bulletin(1, 10, 1234567890)
|
|
hash3 = SecureHash.hash_bulletin(1, 11, 1234567890)
|
|
|
|
assert hash1 == hash2
|
|
assert hash1 != hash3
|