Curso de Criptografía Post-Cuántica

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

Guía de Laboratorio: Configuración de Criptografía Híbrida

Objetivos de Aprendizaje

Requisitos Previos

Nota: Esta práctica implementa sistemas criptográficos híbridos con fines educativos. No debe utilizarse en entornos de producción sin una revisión adecuada.

Introducción Teórica

La criptografía híbrida es un enfoque de transición que combina algoritmos criptográficos clásicos (vulnerables a ataques cuánticos) con algoritmos post-cuánticos (resistentes a ataques cuánticos). Este enfoque proporciona:

  1. Seguridad a prueba de futuro: Protección contra ataques cuánticos futuros.
  2. Compatibilidad hacia atrás: Funcionamiento con sistemas existentes.
  3. Confianza gradual: Permite ganar confianza en los nuevos algoritmos mientras se mantiene la seguridad de los algoritmos probados.

Existen diferentes enfoques para implementar criptografía híbrida:

En esta práctica, implementaremos y analizaremos diferentes enfoques de criptografía híbrida para intercambio de claves y firmas digitales.

Parte 1: Implementación de Intercambio de Claves Híbrido

1.1 Configuración del Entorno

Crea un nuevo archivo Python llamado hybrid_key_exchange.py e importa las bibliotecas necesarias:

import os
import time
import numpy as np
import matplotlib.pyplot as plt
from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# Importar implementaciones simplificadas de ML-KEM
# Nota: Asegúrate de tener el archivo ml_kem_simplified.py de la práctica anterior
from ml_kem_simplified import keygen as ml_kem_keygen
from ml_kem_simplified import encaps as ml_kem_encaps
from ml_kem_simplified import decaps as ml_kem_decaps

1.2 Implementación de Algoritmos Clásicos

Primero, implementaremos funciones para los algoritmos clásicos:

# RSA Key Exchange
def rsa_keygen(key_size=2048):
    """
    Genera un par de claves RSA.
    """
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=key_size
    )
    public_key = private_key.public_key()
    return private_key, public_key

def rsa_encrypt(shared_key, public_key):
    """
    Cifra una clave compartida usando RSA.
    """
    ciphertext = public_key.encrypt(
        shared_key,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    return ciphertext

def rsa_decrypt(ciphertext, private_key):
    """
    Descifra una clave compartida usando RSA.
    """
    shared_key = private_key.decrypt(
        ciphertext,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    return shared_key

# ECDH Key Exchange
def ecdh_keygen():
    """
    Genera un par de claves ECDH.
    """
    private_key = ec.generate_private_key(ec.SECP256R1())
    public_key = private_key.public_key()
    return private_key, public_key

def ecdh_derive_shared_key(private_key, peer_public_key):
    """
    Deriva una clave compartida usando ECDH.
    """
    shared_key = private_key.exchange(ec.ECDH(), peer_public_key)
    # Derivar una clave simétrica usando HKDF
    derived_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=b'handshake data',
    ).derive(shared_key)
    return derived_key

1.3 Implementación de Esquemas Híbridos

Ahora implementaremos diferentes esquemas de criptografía híbrida:

# Esquema 1: Composición en Serie (RSA + ML-KEM)
def serial_hybrid_keygen():
    """
    Genera claves para el esquema híbrido en serie.
    """
    # Generar claves RSA
    rsa_private, rsa_public = rsa_keygen()
    
    # Generar claves ML-KEM
    ml_kem_keys = ml_kem_keygen()
    
    return {
        'rsa': {'private': rsa_private, 'public': rsa_public},
        'ml_kem': ml_kem_keys
    }

def serial_hybrid_encaps(public_keys):
    """
    Encapsula una clave compartida usando el esquema híbrido en serie.
    """
    # Generar clave compartida aleatoria
    shared_key = os.urandom(32)
    
    # Encapsular con ML-KEM
    ml_kem_ct, _ = ml_kem_encaps(public_keys['ml_kem']['public'])
    
    # Cifrar con RSA
    rsa_ct = rsa_encrypt(shared_key, public_keys['rsa']['public'])
    
    return {'rsa': rsa_ct, 'ml_kem': ml_kem_ct}, shared_key

def serial_hybrid_decaps(ciphertext, private_keys):
    """
    Desencapsula una clave compartida usando el esquema híbrido en serie.
    """
    # Descifrar con RSA
    shared_key = rsa_decrypt(ciphertext['rsa'], private_keys['rsa']['private'])
    
    # Desencapsular con ML-KEM (para verificación)
    _ = ml_kem_decaps(ciphertext['ml_kem'], private_keys['ml_kem']['private'], private_keys['ml_kem']['public'])
    
    return shared_key

# Esquema 2: Composición en Paralelo (ECDH + ML-KEM)
def parallel_hybrid_keygen():
    """
    Genera claves para el esquema híbrido en paralelo.
    """
    # Generar claves ECDH
    ecdh_private, ecdh_public = ecdh_keygen()
    
    # Generar claves ML-KEM
    ml_kem_keys = ml_kem_keygen()
    
    return {
        'ecdh': {'private': ecdh_private, 'public': ecdh_public},
        'ml_kem': ml_kem_keys
    }

def parallel_hybrid_encaps(public_keys, peer_private_key):
    """
    Encapsula una clave compartida usando el esquema híbrido en paralelo.
    """
    # Encapsular con ML-KEM
    ml_kem_ct, ml_kem_shared = ml_kem_encaps(public_keys['ml_kem']['public'])
    
    # Derivar clave con ECDH
    ecdh_shared = ecdh_derive_shared_key(peer_private_key, public_keys['ecdh']['public'])
    
    # Combinar ambas claves usando XOR
    combined_key = bytes(a ^ b for a, b in zip(ml_kem_shared, ecdh_shared))
    
    return {'ml_kem': ml_kem_ct}, combined_key

def parallel_hybrid_decaps(ciphertext, private_keys, peer_public_key):
    """
    Desencapsula una clave compartida usando el esquema híbrido en paralelo.
    """
    # Desencapsular con ML-KEM
    ml_kem_shared = ml_kem_decaps(ciphertext['ml_kem'], private_keys['ml_kem']['private'], private_keys['ml_kem']['public'])
    
    # Derivar clave con ECDH
    ecdh_shared = ecdh_derive_shared_key(private_keys['ecdh']['private'], peer_public_key)
    
    # Combinar ambas claves usando XOR
    combined_key = bytes(a ^ b for a, b in zip(ml_kem_shared, ecdh_shared))
    
    return combined_key

# Esquema 3: Composición Integrada (ECDH + ML-KEM con KDF)
def integrated_hybrid_keygen():
    """
    Genera claves para el esquema híbrido integrado.
    """
    # Generar claves ECDH
    ecdh_private, ecdh_public = ecdh_keygen()
    
    # Generar claves ML-KEM
    ml_kem_keys = ml_kem_keygen()
    
    return {
        'ecdh': {'private': ecdh_private, 'public': ecdh_public},
        'ml_kem': ml_kem_keys
    }

def integrated_hybrid_encaps(public_keys, peer_private_key):
    """
    Encapsula una clave compartida usando el esquema híbrido integrado.
    """
    # Encapsular con ML-KEM
    ml_kem_ct, ml_kem_shared = ml_kem_encaps(public_keys['ml_kem']['public'])
    
    # Derivar clave con ECDH
    ecdh_shared = ecdh_derive_shared_key(peer_private_key, public_keys['ecdh']['public'])
    
    # Combinar ambas claves usando KDF
    combined_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=os.urandom(16),
        info=b'hybrid key exchange',
    ).derive(ml_kem_shared + ecdh_shared)
    
    return {'ml_kem': ml_kem_ct}, combined_key

def integrated_hybrid_decaps(ciphertext, private_keys, peer_public_key):
    """
    Desencapsula una clave compartida usando el esquema híbrido integrado.
    """
    # Desencapsular con ML-KEM
    ml_kem_shared = ml_kem_decaps(ciphertext['ml_kem'], private_keys['ml_kem']['private'], private_keys['ml_kem']['public'])
    
    # Derivar clave con ECDH
    ecdh_shared = ecdh_derive_shared_key(private_keys['ecdh']['private'], peer_public_key)
    
    # Combinar ambas claves usando KDF
    combined_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=os.urandom(16),
        info=b'hybrid key exchange',
    ).derive(ml_kem_shared + ecdh_shared)
    
    return combined_key

1.4 Función Principal y Evaluación

Finalmente, implementaremos la función principal para evaluar los diferentes esquemas:

def evaluate_key_exchange_schemes(num_trials=10):
    """
    Evalúa el rendimiento de diferentes esquemas de intercambio de claves.
    """
    # Tiempos para RSA
    rsa_keygen_times = []
    rsa_encaps_times = []
    rsa_decaps_times = []
    
    # Tiempos para ML-KEM
    ml_kem_keygen_times = []
    ml_kem_encaps_times = []
    ml_kem_decaps_times = []
    
    # Tiempos para el esquema híbrido en serie
    serial_keygen_times = []
    serial_encaps_times = []
    serial_decaps_times = []
    
    # Tiempos para el esquema híbrido en paralelo
    parallel_keygen_times = []
    parallel_encaps_times = []
    parallel_decaps_times = []
    
    # Tiempos para el esquema híbrido integrado
    integrated_keygen_times = []
    integrated_encaps_times = []
    integrated_decaps_times = []
    
    for _ in range(num_trials):
        # RSA
        start_time = time.time()
        rsa_private, rsa_public = rsa_keygen()
        rsa_keygen_times.append(time.time() - start_time)
        
        shared_key = os.urandom(32)
        
        start_time = time.time()
        rsa_ct = rsa_encrypt(shared_key, rsa_public)
        rsa_encaps_times.append(time.time() - start_time)
        
        start_time = time.time()
        rsa_decrypt(rsa_ct, rsa_private)
        rsa_decaps_times.append(time.time() - start_time)
        
        # ML-KEM
        start_time = time.time()
        ml_kem_keys = ml_kem_keygen()
        ml_kem_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        ml_kem_ct, ml_kem_shared_sender = ml_kem_encaps(ml_kem_keys['public'])
        ml_kem_encaps_times.append(time.time() - start_time)
        
        start_time = time.time()
        ml_kem_shared_receiver = ml_kem_decaps(ml_kem_ct, ml_kem_keys['private'], ml_kem_keys['public'])
        ml_kem_decaps_times.append(time.time() - start_time)
        
        # Esquema híbrido en serie
        start_time = time.time()
        serial_keys = serial_hybrid_keygen()
        serial_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        serial_ct, serial_shared_sender = serial_hybrid_encaps({
            'rsa': {'public': serial_keys['rsa']['public']},
            'ml_kem': {'public': serial_keys['ml_kem']['public']}
        })
        serial_encaps_times.append(time.time() - start_time)
        
        start_time = time.time()
        serial_shared_receiver = serial_hybrid_decaps(serial_ct, serial_keys)
        serial_decaps_times.append(time.time() - start_time)
        
        # Esquema híbrido en paralelo
        start_time = time.time()
        alice_keys = parallel_hybrid_keygen()
        bob_keys = parallel_hybrid_keygen()
        parallel_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        parallel_ct, parallel_shared_sender = parallel_hybrid_encaps({
            'ecdh': {'public': bob_keys['ecdh']['public']},
            'ml_kem': {'public': bob_keys['ml_kem']['public']}
        }, alice_keys['ecdh']['private'])
        parallel_encaps_times.append(time.time() - start_time)
        
        start_time = time.time()
        parallel_shared_receiver = parallel_hybrid_decaps(parallel_ct, bob_keys, alice_keys['ecdh']['public'])
        parallel_decaps_times.append(time.time() - start_time)
        
        # Esquema híbrido integrado
        start_time = time.time()
        alice_keys_int = integrated_hybrid_keygen()
        bob_keys_int = integrated_hybrid_keygen()
        integrated_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        integrated_ct, integrated_shared_sender = integrated_hybrid_encaps({
            'ecdh': {'public': bob_keys_int['ecdh']['public']},
            'ml_kem': {'public': bob_keys_int['ml_kem']['public']}
        }, alice_keys_int['ecdh']['private'])
        integrated_encaps_times.append(time.time() - start_time)
        
        start_time = time.time()
        integrated_shared_receiver = integrated_hybrid_decaps(integrated_ct, bob_keys_int, alice_keys_int['ecdh']['public'])
        integrated_decaps_times.append(time.time() - start_time)
    
    # Calcular promedios
    rsa_keygen_avg = np.mean(rsa_keygen_times)
    rsa_encaps_avg = np.mean(rsa_encaps_times)
    rsa_decaps_avg = np.mean(rsa_decaps_times)
    
    ml_kem_keygen_avg = np.mean(ml_kem_keygen_times)
    ml_kem_encaps_avg = np.mean(ml_kem_encaps_times)
    ml_kem_decaps_avg = np.mean(ml_kem_decaps_times)
    
    serial_keygen_avg = np.mean(serial_keygen_times)
    serial_encaps_avg = np.mean(serial_encaps_times)
    serial_decaps_avg = np.mean(serial_decaps_times)
    
    parallel_keygen_avg = np.mean(parallel_keygen_times)
    parallel_encaps_avg = np.mean(parallel_encaps_times)
    parallel_decaps_avg = np.mean(parallel_decaps_times)
    
    integrated_keygen_avg = np.mean(integrated_keygen_times)
    integrated_encaps_avg = np.mean(integrated_encaps_times)
    integrated_decaps_avg = np.mean(integrated_decaps_times)
    
    # Visualizar resultados
    plt.figure(figsize=(15, 10))
    
    # Tiempos de generación de claves
    plt.subplot(1, 3, 1)
    plt.bar(['RSA', 'ML-KEM', 'Serie', 'Paralelo', 'Integrado'], 
            [rsa_keygen_avg, ml_kem_keygen_avg, serial_keygen_avg, parallel_keygen_avg, integrated_keygen_avg])
    plt.title('Tiempo de Generación de Claves')
    plt.ylabel('Tiempo (segundos)')
    plt.grid(True, alpha=0.3)
    
    # Tiempos de encapsulamiento
    plt.subplot(1, 3, 2)
    plt.bar(['RSA', 'ML-KEM', 'Serie', 'Paralelo', 'Integrado'], 
            [rsa_encaps_avg, ml_kem_encaps_avg, serial_encaps_avg, parallel_encaps_avg, integrated_encaps_avg])
    plt.title('Tiempo de Encapsulamiento')
    plt.ylabel('Tiempo (segundos)')
    plt.grid(True, alpha=0.3)
    
    # Tiempos de desencapsulamiento
    plt.subplot(1, 3, 3)
    plt.bar(['RSA', 'ML-KEM', 'Serie', 'Paralelo', 'Integrado'], 
            [rsa_decaps_avg, ml_kem_decaps_avg, serial_decaps_avg, parallel_decaps_avg, integrated_decaps_avg])
    plt.title('Tiempo de Desencapsulamiento')
    plt.ylabel('Tiempo (segundos)')
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('hybrid_key_exchange_performance.png')
    plt.show()
    
    # Imprimir resultados
    print("\nComparación de Rendimiento (promedio de", num_trials, "pruebas):")
    
    print("\nRSA:")
    print("  Generación de claves:", rsa_keygen_avg, "segundos")
    print("  Encapsulamiento:", rsa_encaps_avg, "segundos")
    print("  Desencapsulamiento:", rsa_decaps_avg, "segundos")
    print("  Total:", rsa_keygen_avg + rsa_encaps_avg + rsa_decaps_avg, "segundos")
    
    print("\nML-KEM:")
    print("  Generación de claves:", ml_kem_keygen_avg, "segundos")
    print("  Encapsulamiento:", ml_kem_encaps_avg, "segundos")
    print("  Desencapsulamiento:", ml_kem_decaps_avg, "segundos")
    print("  Total:", ml_kem_keygen_avg + ml_kem_encaps_avg + ml_kem_decaps_avg, "segundos")
    
    print("\nEsquema Híbrido en Serie:")
    print("  Generación de claves:", serial_keygen_avg, "segundos")
    print("  Encapsulamiento:", serial_encaps_avg, "segundos")
    print("  Desencapsulamiento:", serial_decaps_avg, "segundos")
    print("  Total:", serial_keygen_avg + serial_encaps_avg + serial_decaps_avg, "segundos")
    
    print("\nEsquema Híbrido en Paralelo:")
    print("  Generación de claves:", parallel_keygen_avg, "segundos")
    print("  Encapsulamiento:", parallel_encaps_avg, "segundos")
    print("  Desencapsulamiento:", parallel_decaps_avg, "segundos")
    print("  Total:", parallel_keygen_avg + parallel_encaps_avg + parallel_decaps_avg, "segundos")
    
    print("\nEsquema Híbrido Integrado:")
    print("  Generación de claves:", integrated_keygen_avg, "segundos")
    print("  Encapsulamiento:", integrated_encaps_avg, "segundos")
    print("  Desencapsulamiento:", integrated_decaps_avg, "segundos")
    print("  Total:", integrated_keygen_avg + integrated_encaps_avg + integrated_decaps_avg, "segundos")

def main():
    print("Evaluación de Esquemas de Intercambio de Claves Híbridos")
    evaluate_key_exchange_schemes()

if __name__ == "__main__":
    main()

Parte 2: Implementación de Firmas Digitales Híbridas

2.1 Configuración del Entorno

Crea un nuevo archivo Python llamado hybrid_signatures.py e importa las bibliotecas necesarias:

import os
import time
import numpy as np
import matplotlib.pyplot as plt
from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding, utils
from cryptography.hazmat.primitives import hashes, serialization

# Importar implementaciones simplificadas de ML-DSA
# Nota: Asegúrate de tener el archivo ml_dsa_simplified.py de la práctica anterior
from ml_dsa_simplified import keygen as ml_dsa_keygen
from ml_dsa_simplified import sign as ml_dsa_sign
from ml_dsa_simplified import verify as ml_dsa_verify

2.2 Implementación de Algoritmos Clásicos

Primero, implementaremos funciones para los algoritmos clásicos:

# RSA Signatures
def rsa_sig_keygen(key_size=2048):
    """
    Genera un par de claves RSA para firmas.
    """
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=key_size
    )
    public_key = private_key.public_key()
    return private_key, public_key

def rsa_sign(message, private_key):
    """
    Firma un mensaje usando RSA.
    """
    if isinstance(message, str):
        message = message.encode()
    
    signature = private_key.sign(
        message,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    return signature

def rsa_verify(message, signature, public_key):
    """
    Verifica una firma RSA.
    """
    if isinstance(message, str):
        message = message.encode()
    
    try:
        public_key.verify(
            signature,
            message,
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
        return True
    except Exception:
        return False

# ECDSA Signatures
def ecdsa_keygen():
    """
    Genera un par de claves ECDSA.
    """
    private_key = ec.generate_private_key(ec.SECP256R1())
    public_key = private_key.public_key()
    return private_key, public_key

def ecdsa_sign(message, private_key):
    """
    Firma un mensaje usando ECDSA.
    """
    if isinstance(message, str):
        message = message.encode()
    
    signature = private_key.sign(
        message,
        ec.ECDSA(hashes.SHA256())
    )
    return signature

def ecdsa_verify(message, signature, public_key):
    """
    Verifica una firma ECDSA.
    """
    if isinstance(message, str):
        message = message.encode()
    
    try:
        public_key.verify(
            signature,
            message,
            ec.ECDSA(hashes.SHA256())
        )
        return True
    except Exception:
        return False

2.3 Implementación de Esquemas Híbridos

Ahora implementaremos diferentes esquemas de firmas híbridas:

# Esquema 1: Composición en Serie (RSA + ML-DSA)
def serial_hybrid_sig_keygen():
    """
    Genera claves para el esquema híbrido de firmas en serie.
    """
    # Generar claves RSA
    rsa_private, rsa_public = rsa_sig_keygen()
    
    # Generar claves ML-DSA
    ml_dsa_keys = ml_dsa_keygen()
    
    return {
        'rsa': {'private': rsa_private, 'public': rsa_public},
        'ml_dsa': ml_dsa_keys
    }

def serial_hybrid_sign(message, private_keys):
    """
    Firma un mensaje usando el esquema híbrido en serie.
    """
    # Firmar con RSA
    rsa_signature = rsa_sign(message, private_keys['rsa']['private'])
    
    # Firmar con ML-DSA
    ml_dsa_signature = ml_dsa_sign(message, private_keys['ml_dsa']['private'])
    
    return {'rsa': rsa_signature, 'ml_dsa': ml_dsa_signature}

def serial_hybrid_verify(message, signature, public_keys):
    """
    Verifica una firma usando el esquema híbrido en serie.
    """
    # Verificar con RSA
    rsa_valid = rsa_verify(message, signature['rsa'], public_keys['rsa']['public'])
    
    # Verificar con ML-DSA
    ml_dsa_valid = ml_dsa_verify(message, signature['ml_dsa'], public_keys['ml_dsa']['public'])
    
    # Ambas firmas deben ser válidas
    return rsa_valid and ml_dsa_valid

# Esquema 2: Composición en Paralelo (ECDSA + ML-DSA)
def parallel_hybrid_sig_keygen():
    """
    Genera claves para el esquema híbrido de firmas en paralelo.
    """
    # Generar claves ECDSA
    ecdsa_private, ecdsa_public = ecdsa_keygen()
    
    # Generar claves ML-DSA
    ml_dsa_keys = ml_dsa_keygen()
    
    return {
        'ecdsa': {'private': ecdsa_private, 'public': ecdsa_public},
        'ml_dsa': ml_dsa_keys
    }

def parallel_hybrid_sign(message, private_keys):
    """
    Firma un mensaje usando el esquema híbrido en paralelo.
    """
    # Firmar con ECDSA
    ecdsa_signature = ecdsa_sign(message, private_keys['ecdsa']['private'])
    
    # Firmar con ML-DSA
    ml_dsa_signature = ml_dsa_sign(message, private_keys['ml_dsa']['private'])
    
    return {'ecdsa': ecdsa_signature, 'ml_dsa': ml_dsa_signature}

def parallel_hybrid_verify(message, signature, public_keys):
    """
    Verifica una firma usando el esquema híbrido en paralelo.
    """
    # Verificar con ECDSA
    ecdsa_valid = ecdsa_verify(message, signature['ecdsa'], public_keys['ecdsa']['public'])
    
    # Verificar con ML-DSA
    ml_dsa_valid = ml_dsa_verify(message, signature['ml_dsa'], public_keys['ml_dsa']['public'])
    
    # Al menos una firma debe ser válida
    return ecdsa_valid or ml_dsa_valid

# Esquema 3: Composición Integrada (ECDSA + ML-DSA con hash combinado)
def integrated_hybrid_sig_keygen():
    """
    Genera claves para el esquema híbrido de firmas integrado.
    """
    # Generar claves ECDSA
    ecdsa_private, ecdsa_public = ecdsa_keygen()
    
    # Generar claves ML-DSA
    ml_dsa_keys = ml_dsa_keygen()
    
    return {
        'ecdsa': {'private': ecdsa_private, 'public': ecdsa_public},
        'ml_dsa': ml_dsa_keys
    }

def integrated_hybrid_sign(message, private_keys):
    """
    Firma un mensaje usando el esquema híbrido integrado.
    """
    if isinstance(message, str):
        message = message.encode()
    
    # Calcular hash del mensaje
    digest = hashes.Hash(hashes.SHA256())
    digest.update(message)
    message_hash = digest.finalize()
    
    # Firmar hash con ECDSA
    ecdsa_signature = ecdsa_sign(message_hash, private_keys['ecdsa']['private'])
    
    # Firmar hash con ML-DSA
    ml_dsa_signature = ml_dsa_sign(message_hash, private_keys['ml_dsa']['private'])
    
    return {'ecdsa': ecdsa_signature, 'ml_dsa': ml_dsa_signature, 'hash': message_hash}

def integrated_hybrid_verify(message, signature, public_keys):
    """
    Verifica una firma usando el esquema híbrido integrado.
    """
    if isinstance(message, str):
        message = message.encode()
    
    # Calcular hash del mensaje
    digest = hashes.Hash(hashes.SHA256())
    digest.update(message)
    message_hash = digest.finalize()
    
    # Verificar que el hash coincide
    if message_hash != signature['hash']:
        return False
    
    # Verificar con ECDSA
    ecdsa_valid = ecdsa_verify(message_hash, signature['ecdsa'], public_keys['ecdsa']['public'])
    
    # Verificar con ML-DSA
    ml_dsa_valid = ml_dsa_verify(message_hash, signature['ml_dsa'], public_keys['ml_dsa']['public'])
    
    # Ambas firmas deben ser válidas
    return ecdsa_valid and ml_dsa_valid

2.4 Función Principal y Evaluación

Finalmente, implementaremos la función principal para evaluar los diferentes esquemas:

def evaluate_signature_schemes(num_trials=10):
    """
    Evalúa el rendimiento de diferentes esquemas de firmas digitales.
    """
    # Mensaje de prueba
    message = "Este es un mensaje de prueba para evaluar esquemas de firmas híbridas."
    
    # Tiempos para RSA
    rsa_keygen_times = []
    rsa_sign_times = []
    rsa_verify_times = []
    
    # Tiempos para ECDSA
    ecdsa_keygen_times = []
    ecdsa_sign_times = []
    ecdsa_verify_times = []
    
    # Tiempos para ML-DSA
    ml_dsa_keygen_times = []
    ml_dsa_sign_times = []
    ml_dsa_verify_times = []
    
    # Tiempos para el esquema híbrido en serie
    serial_keygen_times = []
    serial_sign_times = []
    serial_verify_times = []
    
    # Tiempos para el esquema híbrido en paralelo
    parallel_keygen_times = []
    parallel_sign_times = []
    parallel_verify_times = []
    
    # Tiempos para el esquema híbrido integrado
    integrated_keygen_times = []
    integrated_sign_times = []
    integrated_verify_times = []
    
    # Tamaños de firma
    rsa_signature_sizes = []
    ecdsa_signature_sizes = []
    ml_dsa_signature_sizes = []
    serial_signature_sizes = []
    parallel_signature_sizes = []
    integrated_signature_sizes = []
    
    for _ in range(num_trials):
        # RSA
        start_time = time.time()
        rsa_private, rsa_public = rsa_sig_keygen()
        rsa_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        rsa_signature = rsa_sign(message, rsa_private)
        rsa_sign_times.append(time.time() - start_time)
        
        rsa_signature_sizes.append(len(rsa_signature))
        
        start_time = time.time()
        rsa_verify(message, rsa_signature, rsa_public)
        rsa_verify_times.append(time.time() - start_time)
        
        # ECDSA
        start_time = time.time()
        ecdsa_private, ecdsa_public = ecdsa_keygen()
        ecdsa_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        ecdsa_signature = ecdsa_sign(message, ecdsa_private)
        ecdsa_sign_times.append(time.time() - start_time)
        
        ecdsa_signature_sizes.append(len(ecdsa_signature))
        
        start_time = time.time()
        ecdsa_verify(message, ecdsa_signature, ecdsa_public)
        ecdsa_verify_times.append(time.time() - start_time)
        
        # ML-DSA
        start_time = time.time()
        ml_dsa_keys = ml_dsa_keygen()
        ml_dsa_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        ml_dsa_signature = ml_dsa_sign(message, ml_dsa_keys['private'])
        ml_dsa_sign_times.append(time.time() - start_time)
        
        # Estimación aproximada del tamaño de la firma ML-DSA
        z, hints, c = ml_dsa_signature
        ml_dsa_signature_size = z.nbytes + len(hints) * 8 + c.nbytes
        ml_dsa_signature_sizes.append(ml_dsa_signature_size)
        
        start_time = time.time()
        ml_dsa_verify(message, ml_dsa_signature, ml_dsa_keys['public'])
        ml_dsa_verify_times.append(time.time() - start_time)
        
        # Esquema híbrido en serie
        start_time = time.time()
        serial_keys = serial_hybrid_sig_keygen()
        serial_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        serial_signature = serial_hybrid_sign(message, serial_keys)
        serial_sign_times.append(time.time() - start_time)
        
        serial_signature_size = len(serial_signature['rsa']) + ml_dsa_signature_size
        serial_signature_sizes.append(serial_signature_size)
        
        start_time = time.time()
        serial_hybrid_verify(message, serial_signature, {
            'rsa': {'public': serial_keys['rsa']['public']},
            'ml_dsa': {'public': serial_keys['ml_dsa']['public']}
        })
        serial_verify_times.append(time.time() - start_time)
        
        # Esquema híbrido en paralelo
        start_time = time.time()
        parallel_keys = parallel_hybrid_sig_keygen()
        parallel_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        parallel_signature = parallel_hybrid_sign(message, parallel_keys)
        parallel_sign_times.append(time.time() - start_time)
        
        parallel_signature_size = len(parallel_signature['ecdsa']) + ml_dsa_signature_size
        parallel_signature_sizes.append(parallel_signature_size)
        
        start_time = time.time()
        parallel_hybrid_verify(message, parallel_signature, {
            'ecdsa': {'public': parallel_keys['ecdsa']['public']},
            'ml_dsa': {'public': parallel_keys['ml_dsa']['public']}
        })
        parallel_verify_times.append(time.time() - start_time)
        
        # Esquema híbrido integrado
        start_time = time.time()
        integrated_keys = integrated_hybrid_sig_keygen()
        integrated_keygen_times.append(time.time() - start_time)
        
        start_time = time.time()
        integrated_signature = integrated_hybrid_sign(message, integrated_keys)
        integrated_sign_times.append(time.time() - start_time)
        
        integrated_signature_size = len(integrated_signature['ecdsa']) + ml_dsa_signature_size + 32  # 32 bytes para el hash
        integrated_signature_sizes.append(integrated_signature_size)
        
        start_time = time.time()
        integrated_hybrid_verify(message, integrated_signature, {
            'ecdsa': {'public': integrated_keys['ecdsa']['public']},
            'ml_dsa': {'public': integrated_keys['ml_dsa']['public']}
        })
        integrated_verify_times.append(time.time() - start_time)
    
    # Calcular promedios
    rsa_keygen_avg = np.mean(rsa_keygen_times)
    rsa_sign_avg = np.mean(rsa_sign_times)
    rsa_verify_avg = np.mean(rsa_verify_times)
    rsa_signature_avg = np.mean(rsa_signature_sizes)
    
    ecdsa_keygen_avg = np.mean(ecdsa_keygen_times)
    ecdsa_sign_avg = np.mean(ecdsa_sign_times)
    ecdsa_verify_avg = np.mean(ecdsa_verify_times)
    ecdsa_signature_avg = np.mean(ecdsa_signature_sizes)
    
    ml_dsa_keygen_avg = np.mean(ml_dsa_keygen_times)
    ml_dsa_sign_avg = np.mean(ml_dsa_sign_times)
    ml_dsa_verify_avg = np.mean(ml_dsa_verify_times)
    ml_dsa_signature_avg = np.mean(ml_dsa_signature_sizes)
    
    serial_keygen_avg = np.mean(serial_keygen_times)
    serial_sign_avg = np.mean(serial_sign_times)
    serial_verify_avg = np.mean(serial_verify_times)
    serial_signature_avg = np.mean(serial_signature_sizes)
    
    parallel_keygen_avg = np.mean(parallel_keygen_times)
    parallel_sign_avg = np.mean(parallel_sign_times)
    parallel_verify_avg = np.mean(parallel_verify_times)
    parallel_signature_avg = np.mean(parallel_signature_sizes)
    
    integrated_keygen_avg = np.mean(integrated_keygen_times)
    integrated_sign_avg = np.mean(integrated_sign_times)
    integrated_verify_avg = np.mean(integrated_verify_times)
    integrated_signature_avg = np.mean(integrated_signature_sizes)
    
    # Visualizar resultados
    plt.figure(figsize=(15, 10))
    
    # Tiempos de generación de claves
    plt.subplot(2, 2, 1)
    plt.bar(['RSA', 'ECDSA', 'ML-DSA', 'Serie', 'Paralelo', 'Integrado'], 
            [rsa_keygen_avg, ecdsa_keygen_avg, ml_dsa_keygen_avg, serial_keygen_avg, parallel_keygen_avg, integrated_keygen_avg])
    plt.title('Tiempo de Generación de Claves')
    plt.ylabel('Tiempo (segundos)')
    plt.grid(True, alpha=0.3)
    
    # Tiempos de firma
    plt.subplot(2, 2, 2)
    plt.bar(['RSA', 'ECDSA', 'ML-DSA', 'Serie', 'Paralelo', 'Integrado'], 
            [rsa_sign_avg, ecdsa_sign_avg, ml_dsa_sign_avg, serial_sign_avg, parallel_sign_avg, integrated_sign_avg])
    plt.title('Tiempo de Firma')
    plt.ylabel('Tiempo (segundos)')
    plt.grid(True, alpha=0.3)
    
    # Tiempos de verificación
    plt.subplot(2, 2, 3)
    plt.bar(['RSA', 'ECDSA', 'ML-DSA', 'Serie', 'Paralelo', 'Integrado'], 
            [rsa_verify_avg, ecdsa_verify_avg, ml_dsa_verify_avg, serial_verify_avg, parallel_verify_avg, integrated_verify_avg])
    plt.title('Tiempo de Verificación')
    plt.ylabel('Tiempo (segundos)')
    plt.grid(True, alpha=0.3)
    
    # Tamaños de firma
    plt.subplot(2, 2, 4)
    plt.bar(['RSA', 'ECDSA', 'ML-DSA', 'Serie', 'Paralelo', 'Integrado'], 
            [rsa_signature_avg, ecdsa_signature_avg, ml_dsa_signature_avg, serial_signature_avg, parallel_signature_avg, integrated_signature_avg])
    plt.title('Tamaño de Firma')
    plt.ylabel('Tamaño (bytes)')
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('hybrid_signatures_performance.png')
    plt.show()
    
    # Imprimir resultados
    print("\nComparación de Rendimiento (promedio de", num_trials, "pruebas):")
    
    print("\nRSA:")
    print("  Generación de claves:", rsa_keygen_avg, "segundos")
    print("  Firma:", rsa_sign_avg, "segundos")
    print("  Verificación:", rsa_verify_avg, "segundos")
    print("  Tamaño de firma:", rsa_signature_avg, "bytes")
    
    print("\nECDSA:")
    print("  Generación de claves:", ecdsa_keygen_avg, "segundos")
    print("  Firma:", ecdsa_sign_avg, "segundos")
    print("  Verificación:", ecdsa_verify_avg, "segundos")
    print("  Tamaño de firma:", ecdsa_signature_avg, "bytes")
    
    print("\nML-DSA:")
    print("  Generación de claves:", ml_dsa_keygen_avg, "segundos")
    print("  Firma:", ml_dsa_sign_avg, "segundos")
    print("  Verificación:", ml_dsa_verify_avg, "segundos")
    print("  Tamaño de firma:", ml_dsa_signature_avg, "bytes")
    
    print("\nEsquema Híbrido en Serie:")
    print("  Generación de claves:", serial_keygen_avg, "segundos")
    print("  Firma:", serial_sign_avg, "segundos")
    print("  Verificación:", serial_verify_avg, "segundos")
    print("  Tamaño de firma:", serial_signature_avg, "bytes")
    
    print("\nEsquema Híbrido en Paralelo:")
    print("  Generación de claves:", parallel_keygen_avg, "segundos")
    print("  Firma:", parallel_sign_avg, "segundos")
    print("  Verificación:", parallel_verify_avg, "segundos")
    print("  Tamaño de firma:", parallel_signature_avg, "bytes")
    
    print("\nEsquema Híbrido Integrado:")
    print("  Generación de claves:", integrated_keygen_avg, "segundos")
    print("  Firma:", integrated_sign_avg, "segundos")
    print("  Verificación:", integrated_verify_avg, "segundos")
    print("  Tamaño de firma:", integrated_signature_avg, "bytes")

def main():
    print("Evaluación de Esquemas de Firmas Digitales Híbridos")
    evaluate_signature_schemes()

if __name__ == "__main__":
    main()

Parte 3: Ejercicios y Preguntas de Reflexión

3.1 Ejercicios

  1. Implementa un esquema híbrido que combine RSA y ML-KEM para cifrado de mensajes (no solo intercambio de claves).
  2. Modifica el código para simular un escenario de ataque cuántico donde los algoritmos clásicos son comprometidos pero los post-cuánticos siguen siendo seguros.
  3. Implementa un protocolo TLS simplificado que utilice criptografía híbrida para el establecimiento de la conexión.
  4. Diseña un esquema híbrido que optimice el tamaño de las firmas o el rendimiento, según tus resultados de evaluación.

3.2 Preguntas de Reflexión

  1. ¿Cuál de los esquemas híbridos implementados ofrece el mejor equilibrio entre seguridad, rendimiento y tamaño?
  2. ¿Qué ventajas y desventajas tiene cada enfoque de composición (serie, paralelo, integrado)?
  3. ¿Cómo afectaría la implementación de criptografía híbrida a sistemas existentes en términos de compatibilidad y rendimiento?
  4. ¿Qué estrategias recomendarías para la migración gradual de sistemas criptográficos actuales a soluciones post-cuánticas?
  5. ¿Cuáles son los desafíos prácticos para la implementación de criptografía híbrida en entornos con recursos limitados (IoT, dispositivos móviles, etc.)?

Parte 4: Extensión (Opcional) - Caso de Estudio Práctico

Implementa un sistema de comunicación segura que utilice criptografía híbrida para proteger mensajes:

# Crear un nuevo archivo secure_messaging.py
import os
import time
import json
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding

# Importar nuestras implementaciones híbridas
from hybrid_key_exchange import parallel_hybrid_keygen, parallel_hybrid_encaps, parallel_hybrid_decaps
from hybrid_signatures import parallel_hybrid_sig_keygen, parallel_hybrid_sign, parallel_hybrid_verify

class SecureMessagingSystem:
    def __init__(self):
        # Generar claves para intercambio de claves
        self.key_exchange_keys = parallel_hybrid_keygen()
        
        # Generar claves para firmas
        self.signature_keys = parallel_hybrid_sig_keygen()
    
    def encrypt_message(self, message, recipient_public_keys):
        """
        Cifra un mensaje para un destinatario específico.
        """
        if isinstance(message, str):
            message = message.encode()
        
        # Encapsular una clave compartida
        ciphertext, shared_key = parallel_hybrid_encaps(
            recipient_public_keys['key_exchange'],
            self.key_exchange_keys['ecdh']['private']
        )
        
        # Usar la clave compartida para cifrar el mensaje con AES-GCM
        iv = os.urandom(12)
        encryptor = Cipher(
            algorithms.AES(shared_key[:32]),
            modes.GCM(iv)
        ).encryptor()
        
        # Añadir datos autenticados (AAD)
        encryptor.authenticate_additional_data(b"secure-messaging")
        
        # Cifrar el mensaje
        ciphertext_message = encryptor.update(message) + encryptor.finalize()
        
        # Obtener el tag de autenticación
        tag = encryptor.tag
        
        # Firmar el mensaje cifrado
        signature = parallel_hybrid_sign(ciphertext_message + iv + tag, self.signature_keys)
        
        # Crear el mensaje completo
        encrypted_message = {
            'key_exchange': {
                'ml_kem': ciphertext['ml_kem'].tolist() if hasattr(ciphertext['ml_kem'], 'tolist') else ciphertext['ml_kem']
            },
            'iv': iv.hex(),
            'ciphertext': ciphertext_message.hex(),
            'tag': tag.hex(),
            'signature': {
                'ecdsa': signature['ecdsa'].hex(),
                'ml_dsa': {
                    'z': signature['ml_dsa'][0].tolist() if hasattr(signature['ml_dsa'][0], 'tolist') else signature['ml_dsa'][0],
                    'hints': signature['ml_dsa'][1],
                    'c': signature['ml_dsa'][2].tolist() if hasattr(signature['ml_dsa'][2], 'tolist') else signature['ml_dsa'][2]
                }
            }
        }
        
        return json.dumps(encrypted_message)
    
    def decrypt_message(self, encrypted_message_json, sender_public_keys):
        """
        Descifra un mensaje de un remitente específico.
        """
        # Parsear el mensaje
        encrypted_message = json.loads(encrypted_message_json)
        
        # Convertir datos hexadecimales a bytes
        iv = bytes.fromhex(encrypted_message['iv'])
        ciphertext_message = bytes.fromhex(encrypted_message['ciphertext'])
        tag = bytes.fromhex(encrypted_message['tag'])
        
        # Reconstruir la firma
        signature = {
            'ecdsa': bytes.fromhex(encrypted_message['signature']['ecdsa']),
            'ml_dsa': (
                np.array(encrypted_message['signature']['ml_dsa']['z']),
                encrypted_message['signature']['ml_dsa']['hints'],
                np.array(encrypted_message['signature']['ml_dsa']['c'])
            )
        }
        
        # Verificar la firma
        is_valid = parallel_hybrid_verify(
            ciphertext_message + iv + tag,
            signature,
            sender_public_keys['signature']
        )
        
        if not is_valid:
            raise ValueError("La firma no es válida. El mensaje podría haber sido alterado.")
        
        # Reconstruir el ciphertext de intercambio de claves
        key_exchange_ct = {
            'ml_kem': np.array(encrypted_message['key_exchange']['ml_kem'])
        }
        
        # Desencapsular la clave compartida
        shared_key = parallel_hybrid_decaps(
            key_exchange_ct,
            self.key_exchange_keys,
            sender_public_keys['key_exchange']['ecdh']['public']
        )
        
        # Descifrar el mensaje con AES-GCM
        decryptor = Cipher(
            algorithms.AES(shared_key[:32]),
            modes.GCM(iv, tag)
        ).decryptor()
        
        # Añadir datos autenticados (AAD)
        decryptor.authenticate_additional_data(b"secure-messaging")
        
        # Descifrar el mensaje
        try:
            plaintext = decryptor.update(ciphertext_message) + decryptor.finalize()
            return plaintext
        except Exception as e:
            raise ValueError(f"Error al descifrar el mensaje: {e}")

def main():
    print("Sistema de Mensajería Segura con Criptografía Híbrida")
    
    # Crear instancias para Alice y Bob
    print("\nGenerando claves para Alice...")
    alice = SecureMessagingSystem()
    
    print("Generando claves para Bob...")
    bob = SecureMessagingSystem()
    
    # Intercambiar claves públicas (simulado)
    alice_public_keys = {
        'key_exchange': {
            'ecdh': {'public': alice.key_exchange_keys['ecdh']['public']},
            'ml_kem': {'public': alice.key_exchange_keys['ml_kem']['public']}
        },
        'signature': {
            'ecdsa': {'public': alice.signature_keys['ecdsa']['public']},
            'ml_dsa': {'public': alice.signature_keys['ml_dsa']['public']}
        }
    }
    
    bob_public_keys = {
        'key_exchange': {
            'ecdh': {'public': bob.key_exchange_keys['ecdh']['public']},
            'ml_kem': {'public': bob.key_exchange_keys['ml_kem']['public']}
        },
        'signature': {
            'ecdsa': {'public': bob.signature_keys['ecdsa']['public']},
            'ml_dsa': {'public': bob.signature_keys['ml_dsa']['public']}
        }
    }
    
    # Alice envía un mensaje a Bob
    message = "Hola Bob, este es un mensaje secreto protegido con criptografía híbrida."
    print(f"\nAlice quiere enviar a Bob: '{message}'")
    
    print("\nAlice cifra y firma el mensaje...")
    encrypted_message = alice.encrypt_message(message, bob_public_keys)
    
    print("Mensaje cifrado y firmado enviado a Bob.")
    
    print("\nBob verifica la firma y descifra el mensaje...")
    try:
        decrypted_message = bob.decrypt_message(encrypted_message, alice_public_keys)
        print(f"Bob ha descifrado: '{decrypted_message.decode()}'")
        print("\n¡Comunicación segura establecida con éxito!")
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()

Entregables

Al finalizar esta práctica, deberás entregar:

  1. Código fuente de las implementaciones (hybrid_key_exchange.py, hybrid_signatures.py y secure_messaging.py)
  2. Capturas de pantalla o gráficos generados durante la ejecución
  3. Un informe breve (máximo 3 páginas) que incluya:
    • Resultados obtenidos en las pruebas de rendimiento
    • Análisis comparativo entre los diferentes esquemas híbridos
    • Respuestas a las preguntas de reflexión
    • Conclusiones sobre la viabilidad de la criptografía híbrida para aplicaciones prácticas

Recursos Adicionales