163 lines
4.8 KiB
Python
163 lines
4.8 KiB
Python
"""
|
|
Primitives de chiffrement : ElGamal, chiffrement homomorphe, AES.
|
|
"""
|
|
|
|
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
from cryptography.hazmat.backends import default_backend
|
|
from cryptography.hazmat.primitives import hashes
|
|
import os
|
|
from typing import Tuple
|
|
from dataclasses import dataclass
|
|
import json
|
|
|
|
|
|
@dataclass
|
|
class PublicKey:
|
|
"""Clé publique ElGamal"""
|
|
p: int # Nombre premier
|
|
g: int # Générateur du groupe
|
|
h: int # Clé publique = g^x mod p
|
|
|
|
|
|
@dataclass
|
|
class PrivateKey:
|
|
"""Clé privée ElGamal"""
|
|
x: int # Clé privée
|
|
|
|
|
|
@dataclass
|
|
class Ciphertext:
|
|
"""Texte chiffré ElGamal"""
|
|
c1: int # c1 = g^r mod p
|
|
c2: int # c2 = m * h^r mod p
|
|
|
|
|
|
class ElGamalEncryption:
|
|
"""
|
|
Chiffrement ElGamal - Fondamental pour le vote électronique.
|
|
Propriétés:
|
|
- Sémantiquement sûr (IND-CPA)
|
|
- Chiffrement probabiliste
|
|
- Support pour preuves ZK
|
|
"""
|
|
|
|
def __init__(self, p: int = None, g: int = None):
|
|
"""
|
|
Initialiser ElGamal avec paramètres de groupe.
|
|
Pour le prototype, utilise des paramètres de test.
|
|
"""
|
|
if p is None:
|
|
# Nombres premiers de test (petits, pour prototype)
|
|
# En production: nombres premiers cryptographiques forts (2048+ bits)
|
|
self.p = 23 # Nombre premier
|
|
self.g = 5 # Générateur
|
|
else:
|
|
self.p = p
|
|
self.g = g
|
|
|
|
def generate_keypair(self) -> Tuple[PublicKey, PrivateKey]:
|
|
"""Générer une paire de clés ElGamal"""
|
|
import random
|
|
x = random.randint(2, self.p - 2) # Clé privée
|
|
h = pow(self.g, x, self.p) # Clé publique: g^x mod p
|
|
|
|
public = PublicKey(p=self.p, g=self.g, h=h)
|
|
private = PrivateKey(x=x)
|
|
|
|
return public, private
|
|
|
|
def encrypt(self, public_key: PublicKey, message: int) -> Ciphertext:
|
|
"""
|
|
Chiffrer un message avec ElGamal.
|
|
message: nombre entre 0 et p-1
|
|
"""
|
|
import random
|
|
r = random.randint(2, self.p - 2) # Aléa
|
|
|
|
c1 = pow(self.g, r, self.p) # c1 = g^r mod p
|
|
c2 = (message * pow(public_key.h, r, self.p)) % self.p # c2 = m * h^r mod p
|
|
|
|
return Ciphertext(c1=c1, c2=c2)
|
|
|
|
def decrypt(self, private_key: PrivateKey, ciphertext: Ciphertext, p: int) -> int:
|
|
"""Déchiffrer un message ElGamal"""
|
|
# m = c2 / c1^x mod p = c2 * (c1^x)^(-1) mod p
|
|
shared_secret = pow(ciphertext.c1, private_key.x, p)
|
|
shared_secret_inv = pow(shared_secret, -1, p)
|
|
message = (ciphertext.c2 * shared_secret_inv) % p
|
|
|
|
return message
|
|
|
|
def add_ciphertexts(self, ct1: Ciphertext, ct2: Ciphertext, p: int) -> Ciphertext:
|
|
"""
|
|
Addition homomorphe : E(m1) * E(m2) = E(m1 + m2)
|
|
Propriété clé pour les dépouillement sécurisé
|
|
"""
|
|
c1_sum = (ct1.c1 * ct2.c1) % p
|
|
c2_sum = (ct1.c2 * ct2.c2) % p
|
|
return Ciphertext(c1=c1_sum, c2=c2_sum)
|
|
|
|
|
|
class HomomorphicEncryption:
|
|
"""
|
|
Chiffrement homomorphe - Paillier-like pour vote.
|
|
Support l'addition sans déchiffrement.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.elgamal = ElGamalEncryption()
|
|
|
|
def sum_encrypted_votes(self, ciphertexts: list[Ciphertext], p: int) -> Ciphertext:
|
|
"""
|
|
Additionner les votes chiffrés sans les déchiffrer.
|
|
C'est la base du dépouillement sécurisé.
|
|
"""
|
|
result = ciphertexts[0]
|
|
for ct in ciphertexts[1:]:
|
|
result = self.elgamal.add_ciphertexts(result, ct, p)
|
|
return result
|
|
|
|
|
|
class SymmetricEncryption:
|
|
"""
|
|
Chiffrement symétrique AES-256 pour données sensibles.
|
|
"""
|
|
|
|
@staticmethod
|
|
def generate_key() -> bytes:
|
|
"""Générer une clé AES-256 aléatoire"""
|
|
return os.urandom(32)
|
|
|
|
@staticmethod
|
|
def encrypt(key: bytes, plaintext: bytes) -> bytes:
|
|
"""Chiffrer avec AES-256-GCM"""
|
|
iv = os.urandom(16)
|
|
cipher = Cipher(
|
|
algorithms.AES(key),
|
|
modes.GCM(iv),
|
|
backend=default_backend()
|
|
)
|
|
encryptor = cipher.encryptor()
|
|
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
|
|
|
|
# Retourner IV || tag || ciphertext
|
|
return iv + encryptor.tag + ciphertext
|
|
|
|
@staticmethod
|
|
def decrypt(key: bytes, encrypted_data: bytes) -> bytes:
|
|
"""Déchiffrer AES-256-GCM"""
|
|
iv = encrypted_data[:16]
|
|
tag = encrypted_data[16:32]
|
|
ciphertext = encrypted_data[32:]
|
|
|
|
cipher = Cipher(
|
|
algorithms.AES(key),
|
|
modes.GCM(iv, tag),
|
|
backend=default_backend()
|
|
)
|
|
decryptor = cipher.decryptor()
|
|
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
|
|
|
return plaintext
|