280 lines
9.5 KiB
Python

"""
Cryptographie Post-Quantique Hybride - Standards NIST FIPS 203/204/205
Combines classical et quantum-resistant cryptography:
- Chiffrement: ElGamal (classique) + Kyber (post-quantique)
- Signatures: RSA-PSS (classique) + Dilithium (post-quantique)
- Hachage: SHA-256 (résistant aux ordinateurs quantiques pour préimage)
Cette approche hybride garantit que même si l'un des systèmes est cassé,
l'autre reste sûr (defense-in-depth).
"""
try:
import oqs
HAS_OQS = True
except ImportError:
HAS_OQS = False
import os
from typing import Tuple, Dict, Any
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
from .encryption import ElGamalEncryption
from .hashing import SecureHash
class PostQuantumCryptography:
"""
Implémentation hybride de cryptographie post-quantique.
Utilise les standards NIST FIPS 203/204/205.
"""
# Algorithmes post-quantiques certifiés NIST
PQC_SIGN_ALG = "ML-DSA-65" # FIPS 204 - Dilithium variant
PQC_KEM_ALG = "ML-KEM-768" # FIPS 203 - Kyber variant
@staticmethod
def generate_hybrid_keypair() -> Dict[str, Any]:
"""
Générer une paire de clés hybride:
- Clés RSA classiques + clés Dilithium PQC
- Clés ElGamal classiques + clés Kyber PQC
Returns:
Dict contenant toutes les clés publiques et privées
"""
# Générer clés RSA classiques (2048 bits)
rsa_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# Générer clés Dilithium (signatures PQC)
with oqs.KeyEncapsulation(PostQuantumCryptography.PQC_SIGN_ALG) as kemsign:
dilithium_public = kemsign.generate_keypair()
dilithium_secret = kemsign.export_secret_key()
# Générer clés Kyber (chiffrement PQC)
with oqs.KeyEncapsulation(PostQuantumCryptography.PQC_KEM_ALG) as kemenc:
kyber_public = kemenc.generate_keypair()
kyber_secret = kemenc.export_secret_key()
# Générer clés ElGamal classiques
elgamal = ElGamalEncryption()
elgamal_public, elgamal_secret = elgamal.generate_keypair()
return {
# Clés classiques
"rsa_public_key": rsa_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
),
"rsa_private_key": rsa_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
),
"elgamal_public": elgamal_public,
"elgamal_secret": elgamal_secret,
# Clés post-quantiques
"dilithium_public": dilithium_public.hex(), # Serialisé en hex
"dilithium_secret": dilithium_secret.hex(),
"kyber_public": kyber_public.hex(),
"kyber_secret": kyber_secret.hex(),
}
@staticmethod
def hybrid_sign(
message: bytes,
rsa_private_key: bytes,
dilithium_secret: str
) -> Dict[str, bytes]:
"""
Signer un message avec signatures hybrides:
1. Signature RSA-PSS classique
2. Signature Dilithium post-quantique
Args:
message: Le message à signer
rsa_private_key: Clé privée RSA (PEM)
dilithium_secret: Clé secrète Dilithium (hex)
Returns:
Dict avec les deux signatures
"""
from cryptography.hazmat.primitives.serialization import load_pem_private_key
# Signature RSA-PSS classique
rsa_key = load_pem_private_key(
rsa_private_key,
password=None,
backend=default_backend()
)
rsa_signature = rsa_key.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
# Signature Dilithium post-quantique
dilithium_secret_bytes = bytes.fromhex(dilithium_secret)
with oqs.Signature(PostQuantumCryptography.PQC_SIGN_ALG) as sig:
sig.secret_key = dilithium_secret_bytes
dilithium_signature = sig.sign(message)
return {
"rsa_signature": rsa_signature,
"dilithium_signature": dilithium_signature,
"algorithm": "Hybrid(RSA-PSS + ML-DSA-65)"
}
@staticmethod
def hybrid_verify(
message: bytes,
signatures: Dict[str, bytes],
rsa_public_key: bytes,
dilithium_public: str
) -> bool:
"""
Vérifier les signatures hybrides.
Les deux signatures doivent être valides.
Args:
message: Le message signé
signatures: Dict avec rsa_signature et dilithium_signature
rsa_public_key: Clé publique RSA (PEM)
dilithium_public: Clé publique Dilithium (hex)
Returns:
True si les deux signatures sont valides
"""
from cryptography.hazmat.primitives.serialization import load_pem_public_key
try:
# Vérifier signature RSA-PSS
rsa_key = load_pem_public_key(rsa_public_key, default_backend())
rsa_key.verify(
signatures["rsa_signature"],
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
# Vérifier signature Dilithium
dilithium_public_bytes = bytes.fromhex(dilithium_public)
with oqs.Signature(PostQuantumCryptography.PQC_SIGN_ALG) as sig:
sig.public_key = dilithium_public_bytes
sig.verify(message, signatures["dilithium_signature"])
return True
except Exception as e:
print(f"Signature verification failed: {e}")
return False
@staticmethod
def hybrid_encapsulate(
kyber_public: str,
elgamal_public: Tuple[int, int, int]
) -> Dict[str, Any]:
"""
Encapsuler un secret avec chiffrement hybride:
1. Kyber pour le chiffrement PQC
2. ElGamal pour le chiffrement classique
Args:
kyber_public: Clé publique Kyber (hex)
elgamal_public: Clé publique ElGamal (p, g, h)
Returns:
Dict avec ciphertexts et secret encapsulé
"""
kyber_public_bytes = bytes.fromhex(kyber_public)
# Encapsulation Kyber
with oqs.KeyEncapsulation(PostQuantumCryptography.PQC_KEM_ALG) as kem:
kem.public_key = kyber_public_bytes
kyber_ciphertext, kyber_secret = kem.encap_secret()
# Encapsulation ElGamal (chiffrement d'un secret aléatoire)
message = os.urandom(32) # Secret aléatoire 256-bit
elgamal = ElGamalEncryption()
elgamal_ciphertext = elgamal.encrypt(elgamal_public, message)
# Dériver une clé finale à partir des deux secrets
combined_secret = SecureHash.sha256(
kyber_secret + message
)
return {
"kyber_ciphertext": kyber_ciphertext.hex(),
"elgamal_ciphertext": elgamal_ciphertext,
"combined_secret": combined_secret,
"algorithm": "Hybrid(Kyber + ElGamal)"
}
@staticmethod
def hybrid_decapsulate(
ciphertexts: Dict[str, Any],
kyber_secret: str,
elgamal_secret: int
) -> bytes:
"""
Décapsuler et récupérer le secret hybride:
1. Décapsuler Kyber
2. Décapsuler ElGamal
3. Combiner les deux secrets
Args:
ciphertexts: Dict avec kyber_ciphertext et elgamal_ciphertext
kyber_secret: Clé secrète Kyber (hex)
elgamal_secret: Clé secrète ElGamal (x)
Returns:
Le secret déchiffré
"""
kyber_secret_bytes = bytes.fromhex(kyber_secret)
kyber_ciphertext_bytes = bytes.fromhex(ciphertexts["kyber_ciphertext"])
# Décapsulation Kyber
with oqs.KeyEncapsulation(PostQuantumCryptography.PQC_KEM_ALG) as kem:
kem.secret_key = kyber_secret_bytes
kyber_shared_secret = kem.decap_secret(kyber_ciphertext_bytes)
# Décapsulation ElGamal
elgamal = ElGamalEncryption()
elgamal_message = elgamal.decrypt(
elgamal_secret,
ciphertexts["elgamal_ciphertext"]
)
# Combiner les secrets
combined_secret = SecureHash.sha256(
kyber_shared_secret + elgamal_message
)
return combined_secret
@staticmethod
def get_algorithm_info() -> Dict[str, str]:
"""Afficher les informations sur les algorithmes utilisés"""
return {
"signatures": "Hybrid(RSA-PSS 2048-bit + ML-DSA-65/Dilithium)",
"signatures_status": "FIPS 204 certified",
"encryption": "Hybrid(ElGamal + ML-KEM-768/Kyber)",
"encryption_status": "FIPS 203 certified",
"hashing": "SHA-256",
"hashing_quantum_resistance": "Quantum-resistant (preimage security)",
"security_level": "Post-Quantum + Classical hybrid",
"defense": "Defense-in-depth: compromise d'un système ne casse pas l'autre"
}