Curso de Criptografía Post-Cuántica

Un enfoque introductorio a la seguridad en la era cuántica

Criptografía Híbrida: Combinando Algoritmos Clásicos y Post-Cuánticos

La criptografía híbrida combina algoritmos criptográficos clásicos (como RSA o ECDH) con algoritmos post-cuánticos (como ML-KEM) para proporcionar seguridad tanto contra ataques clásicos como cuánticos durante el período de transición hacia la criptografía post-cuántica.

En este ejemplo, exploraremos diferentes enfoques para implementar criptografía híbrida y veremos una implementación simplificada que ilustra cómo se pueden combinar estos algoritmos en la práctica.

Implementación Simplificada de Criptografía Híbrida

El siguiente código es una implementación didáctica simplificada de criptografía híbrida que combina ECDH (curvas elípticas) con ML-KEM. No es criptográficamente segura y solo tiene fines educativos.

"""
Implementación didáctica simplificada de Criptografía Híbrida (ECDH + ML-KEM)
NOTA: Esta implementación es solo para fines educativos y no es criptográficamente segura.
"""
import os
import hashlib
import numpy as np
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

# Simulación simplificada de ML-KEM (basada en el estándar FIPS 203)
class SimplifiedMLKEM:
    def __init__(self, security_level=128):
        self.n = 256  # Grado del polinomio
        self.q = 3329  # Módulo primo
        self.security_level = security_level
    
    def keygen(self):
        """Genera un par de claves ML-KEM"""
        # Generar semilla aleatoria
        seed = os.urandom(32)
        
        # Simular generación de matriz A y vector secreto s
        np.random.seed(int.from_bytes(seed, byteorder='big'))
        A = np.random.randint(0, self.q, (self.n, self.n))
        s = np.random.randint(-2, 3, self.n)  # Coeficientes pequeños
        e = np.random.randint(-2, 3, self.n)  # Error pequeño
        
        # Calcular clave pública t = A·s + e
        t = (A.dot(s) + e) % self.q
        
        # Clave pública: (seed, t)
        # Clave privada: s
        return {
            'public': {'seed': seed, 't': t},
            'private': {'s': s}
        }
    
    def encapsulate(self, public_key):
        """Encapsula una clave compartida usando la clave pública"""
        seed = public_key['seed']
        t = public_key['t']
        
        # Reconstruir matriz A
        np.random.seed(int.from_bytes(seed, byteorder='big'))
        A = np.random.randint(0, self.q, (self.n, self.n))
        
        # Generar vector efímero r y errores
        r = np.random.randint(-2, 3, self.n)
        e1 = np.random.randint(-2, 3, self.n)
        e2 = np.random.randint(-2, 3, self.n)
        
        # Calcular u = A^T·r + e1
        u = (A.T.dot(r) + e1) % self.q
        
        # Calcular v = t^T·r + e2 + encode(m)
        # Para simplificar, usamos un mensaje aleatorio m
        m = os.urandom(32)
        m_encoded = int.from_bytes(m, byteorder='big') % 2  # Solo usamos 1 bit
        v = (np.dot(t, r) + e2 + m_encoded * (self.q // 2)) % self.q
        
        # Derivar clave compartida de m
        shared_key = hashlib.sha256(m).digest()
        
        # Ciphertext: (u, v)
        return {
            'ciphertext': {'u': u, 'v': v},
            'shared_key': shared_key
        }
    
    def decapsulate(self, ciphertext, private_key):
        """Decapsula una clave compartida usando la clave privada"""
        u = ciphertext['u']
        v = ciphertext['v']
        s = private_key['s']
        
        # Calcular v - s^T·u
        w = (v - np.dot(s, u)) % self.q
        
        # Decodificar mensaje
        # Si w está cerca de q/2, m' = 1; si está cerca de 0, m' = 0
        if abs(w - self.q // 2) < abs(w):
            m_bit = 1
        else:
            m_bit = 0
        
        # Reconstruir m
        m = m_bit.to_bytes(32, byteorder='big')
        
        # Derivar clave compartida de m
        shared_key = hashlib.sha256(m).digest()
        
        return shared_key

# Implementación de criptografía híbrida
class HybridCryptography:
    def __init__(self, mode="parallel"):
        """
        Inicializa el sistema de criptografía híbrida
        
        Modos:
        - "parallel": ECDH y ML-KEM se ejecutan en paralelo, las claves se combinan
        - "serial": La clave ECDH se usa para cifrar la clave ML-KEM
        - "integrated": Se usa una KDF para combinar las claves ECDH y ML-KEM
        """
        self.mode = mode
        self.ml_kem = SimplifiedMLKEM()
    
    def keygen(self):
        """Genera pares de claves híbridas"""
        # Generar par de claves ECDH
        ecdh_private_key = ec.generate_private_key(ec.SECP256R1())
        ecdh_public_key = ecdh_private_key.public_key()
        
        # Generar par de claves ML-KEM
        ml_kem_keys = self.ml_kem.keygen()
        
        return {
            'ecdh': {
                'private': ecdh_private_key,
                'public': ecdh_public_key
            },
            'ml_kem': ml_kem_keys
        }
    
    def encapsulate(self, public_keys):
        """Encapsula una clave compartida híbrida"""
        ecdh_public_key = public_keys['ecdh']['public']
        ml_kem_public_key = public_keys['ml_kem']['public']
        
        # Generar clave efímera ECDH
        ecdh_ephemeral_private = ec.generate_private_key(ec.SECP256R1())
        ecdh_ephemeral_public = ecdh_ephemeral_private.public_key()
        
        # Calcular secreto compartido ECDH
        ecdh_shared_secret = ecdh_ephemeral_private.exchange(
            ec.ECDH(), ecdh_public_key
        )
        
        # Encapsular clave ML-KEM
        ml_kem_result = self.ml_kem.encapsulate(ml_kem_public_key)
        ml_kem_ciphertext = ml_kem_result['ciphertext']
        ml_kem_shared_key = ml_kem_result['shared_key']
        
        # Combinar claves según el modo
        if self.mode == "parallel":
            # Modo paralelo: concatenar y derivar
            combined_key = self._derive_combined_key(
                ecdh_shared_secret + ml_kem_shared_key
            )
        elif self.mode == "serial":
            # Modo serial: cifrar ML-KEM con ECDH
            # (simplificado: XOR con hash de ECDH)
            ecdh_key_hash = hashlib.sha256(ecdh_shared_secret).digest()
            combined_key = bytes(a ^ b for a, b in zip(ml_kem_shared_key, ecdh_key_hash))
        else:  # "integrated"
            # Modo integrado: usar KDF con ambas entradas
            combined_key = self._derive_integrated_key(
                ecdh_shared_secret, ml_kem_shared_key
            )
        
        return {
            'ecdh_ephemeral_public': ecdh_ephemeral_public,
            'ml_kem_ciphertext': ml_kem_ciphertext,
            'shared_key': combined_key
        }
    
    def decapsulate(self, encapsulation, private_keys):
        """Decapsula una clave compartida híbrida"""
        ecdh_private_key = private_keys['ecdh']['private']
        ml_kem_private_key = private_keys['ml_kem']['private']
        
        ecdh_ephemeral_public = encapsulation['ecdh_ephemeral_public']
        ml_kem_ciphertext = encapsulation['ml_kem_ciphertext']
        
        # Calcular secreto compartido ECDH
        ecdh_shared_secret = ecdh_private_key.exchange(
            ec.ECDH(), ecdh_ephemeral_public
        )
        
        # Decapsular clave ML-KEM
        ml_kem_shared_key = self.ml_kem.decapsulate(
            ml_kem_ciphertext, ml_kem_private_key
        )
        
        # Combinar claves según el modo
        if self.mode == "parallel":
            # Modo paralelo: concatenar y derivar
            combined_key = self._derive_combined_key(
                ecdh_shared_secret + ml_kem_shared_key
            )
        elif self.mode == "serial":
            # Modo serial: descifrar ML-KEM con ECDH
            # (simplificado: XOR con hash de ECDH)
            ecdh_key_hash = hashlib.sha256(ecdh_shared_secret).digest()
            combined_key = bytes(a ^ b for a, b in zip(ml_kem_shared_key, ecdh_key_hash))
        else:  # "integrated"
            # Modo integrado: usar KDF con ambas entradas
            combined_key = self._derive_integrated_key(
                ecdh_shared_secret, ml_kem_shared_key
            )
        
        return combined_key
    
    def _derive_combined_key(self, key_material):
        """Deriva una clave combinada a partir del material de clave"""
        return HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=None,
            info=b'hybrid_key'
        ).derive(key_material)
    
    def _derive_integrated_key(self, ecdh_key, ml_kem_key):
        """Deriva una clave integrada a partir de ambas claves"""
        # Usar HKDF con dos extractos
        ecdh_extract = HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=None,
            info=b'ecdh_extract'
        ).derive(ecdh_key)
        
        return HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=ecdh_extract,  # Usar extracto ECDH como sal
            info=b'hybrid_integrated_key'
        ).derive(ml_kem_key)

# Ejemplo de uso
def simulate_quantum_attack(hybrid_crypto, encapsulation, alice_keys, bob_keys):
    """Simula un ataque cuántico que compromete ECDH pero no ML-KEM"""
    print("\nSimulando ataque cuántico...")
    
    # En un ataque real con computadora cuántica, el atacante podría:
    # 1. Usar el algoritmo de Shor para romper ECDH
    # 2. Recuperar la clave privada ECDH de Bob
    # 3. Calcular el mismo secreto compartido ECDH
    
    # Simulamos que el atacante ha obtenido la clave privada ECDH
    ecdh_private_key = bob_keys['ecdh']['private']
    ecdh_ephemeral_public = encapsulation['ecdh_ephemeral_public']
    
    # El atacante calcula el mismo secreto compartido ECDH
    ecdh_shared_secret = ecdh_private_key.exchange(
        ec.ECDH(), ecdh_ephemeral_public
    )
    
    print("El atacante ha comprometido la clave ECDH.")
    
    # Verificar si el atacante puede recuperar la clave final
    if hybrid_crypto.mode == "parallel":
        print("Modo paralelo: El atacante no puede recuperar la clave final sin la parte ML-KEM.")
        return False
    elif hybrid_crypto.mode == "serial":
        print("Modo serial: El atacante no puede recuperar la clave final sin descifrar ML-KEM.")
        return False
    else:  # "integrated"
        print("Modo integrado: El atacante no puede recuperar la clave final sin la parte ML-KEM.")
        return False

if __name__ == "__main__":
    # Probar los tres modos de criptografía híbrida
    modes = ["parallel", "serial", "integrated"]
    
    for mode in modes:
        print(f"\n=== Modo {mode} ===")
        
        # Inicializar criptografía híbrida
        hybrid_crypto = HybridCryptography(mode=mode)
        
        # Alice genera sus claves
        alice_keys = hybrid_crypto.keygen()
        print("Alice ha generado sus claves híbridas.")
        
        # Bob genera sus claves
        bob_keys = hybrid_crypto.keygen()
        print("Bob ha generado sus claves híbridas.")
        
        # Alice encapsula una clave para Bob
        encapsulation = hybrid_crypto.encapsulate(bob_keys)
        alice_shared_key = encapsulation['shared_key']
        print("Alice ha encapsulado una clave compartida para Bob.")
        
        # Bob decapsula la clave
        bob_shared_key = hybrid_crypto.decapsulate(encapsulation, bob_keys)
        print("Bob ha decapsulado la clave compartida.")
        
        # Verificar que ambos tengan la misma clave
        if alice_shared_key == bob_shared_key:
            print("¡Éxito! Alice y Bob comparten la misma clave secreta.")
        else:
            print("Error: Las claves no coinciden.")
        
        # Simular ataque cuántico
        attack_success = simulate_quantum_attack(
            hybrid_crypto, encapsulation, alice_keys, bob_keys
        )
        
        if attack_success:
            print("¡Ataque exitoso! El atacante ha recuperado la clave compartida.")
        else:
            print("Ataque fallido. La criptografía híbrida ha protegido la clave compartida.")

Explicación de la Criptografía Híbrida

¿Por qué Criptografía Híbrida?

La criptografía híbrida ofrece varias ventajas durante la transición hacia la criptografía post-cuántica:

Enfoques para la Criptografía Híbrida

Existen tres enfoques principales para implementar criptografía híbrida:

1. Enfoque en Paralelo

Diagrama de enfoque en paralelo

Funcionamiento: Se ejecutan ambos algoritmos de forma independiente y se combinan las claves resultantes.

Ventajas: Simple de implementar, mantiene la seguridad del algoritmo más fuerte.

Desventajas: Mayor sobrecarga en comunicación (dos conjuntos de claves públicas y ciphertexts).

2. Enfoque en Serie

Diagrama de enfoque en serie

Funcionamiento: Un algoritmo protege la clave del otro (típicamente, RSA/ECDH cifra la clave ML-KEM).

Ventajas: Menor sobrecarga en comunicación que el enfoque paralelo.

Desventajas: Si el algoritmo exterior se rompe, la seguridad depende completamente del algoritmo interior.

3. Enfoque Integrado

Diagrama de enfoque integrado

Funcionamiento: Se utiliza una función de derivación de claves (KDF) para combinar los secretos de ambos algoritmos.

Ventajas: Mayor flexibilidad y seguridad, permite personalizar cómo se combinan las claves.

Desventajas: Más complejo de implementar correctamente.

Comparación de Seguridad

Escenario Algoritmo Clásico (ECDH) Algoritmo PQ (ML-KEM) Híbrido Paralelo Híbrido Serie Híbrido Integrado
Atacante Clásico Seguro Seguro Seguro Seguro Seguro
Atacante Cuántico Vulnerable Seguro Seguro Seguro Seguro
Vulnerabilidad en Clásico Comprometido No afectado Seguro Seguro Seguro
Vulnerabilidad en PQ No afectado Comprometido Seguro Comprometido Seguro

Aplicaciones Prácticas

La criptografía híbrida ya está siendo implementada en varios contextos:

El NIST y otras organizaciones de estandarización están trabajando en guías oficiales para la implementación de criptografía híbrida durante la transición a la criptografía post-cuántica.

Demostración Interactiva: Criptografía Híbrida

Estado

Esperando acción...

Claves

Encapsulamiento

Clave Compartida

Resultados del Ataque