Curso de Criptografía Post-Cuántica

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

Distribución Cuántica de Claves: Protocolo BB84

La Distribución Cuántica de Claves (QKD, por sus siglas en inglés) es un método para compartir claves criptográficas que utiliza principios de la mecánica cuántica para garantizar la seguridad. A diferencia de la criptografía post-cuántica, que utiliza matemáticas clásicas resistentes a ataques cuánticos, QKD aprovecha las propiedades fundamentales de la física cuántica.

El protocolo BB84, propuesto por Bennett y Brassard en 1984, fue el primer protocolo QKD y sigue siendo uno de los más importantes. En este ejemplo, exploraremos cómo funciona BB84 y veremos una implementación simplificada que ilustra sus principios fundamentales.

Implementación Simplificada del Protocolo BB84

El siguiente código es una implementación didáctica simplificada del protocolo BB84. No es una implementación real de QKD (que requeriría hardware cuántico), sino una simulación para ilustrar los conceptos.

"""
Simulación didáctica del protocolo BB84 para Distribución Cuántica de Claves
NOTA: Esta implementación es solo para fines educativos y simula los efectos cuánticos.
"""
import numpy as np
import random
import hashlib
from enum import Enum

class Basis(Enum):
    RECTILINEAR = 0  # Base + (horizontal/vertical)
    DIAGONAL = 1     # Base × (diagonal)

class Qubit:
    def __init__(self, bit_value, basis):
        """
        Inicializa un qubit con un valor de bit y una base de medición
        
        Args:
            bit_value (int): 0 o 1
            basis (Basis): Base de preparación (RECTILINEAR o DIAGONAL)
        """
        self.bit_value = bit_value
        self.basis = basis
    
    def measure(self, measurement_basis):
        """
        Mide el qubit en la base especificada
        
        Args:
            measurement_basis (Basis): Base de medición
            
        Returns:
            int: Resultado de la medición (0 o 1)
        """
        if measurement_basis == self.basis:
            # Si medimos en la misma base, obtenemos el valor original
            return self.bit_value
        else:
            # Si medimos en una base diferente, obtenemos un resultado aleatorio
            return random.randint(0, 1)
    
    def __str__(self):
        basis_symbol = "+" if self.basis == Basis.RECTILINEAR else "×"
        return f"{self.bit_value}{basis_symbol}"

class QuantumChannel:
    def __init__(self, error_rate=0.0, eve_present=False):
        """
        Inicializa un canal cuántico con una tasa de error y presencia de espía
        
        Args:
            error_rate (float): Tasa de error del canal (0.0 a 1.0)
            eve_present (bool): Si hay un espía (Eve) en el canal
        """
        self.error_rate = error_rate
        self.eve_present = eve_present
        self.eve_bases = []  # Bases que Eve usará para medir
    
    def transmit(self, qubits):
        """
        Transmite qubits a través del canal
        
        Args:
            qubits (list): Lista de objetos Qubit
            
        Returns:
            list: Lista de qubits potencialmente alterados
        """
        transmitted_qubits = []
        
        # Si Eve está presente, genera bases aleatorias para medir
        if self.eve_present:
            self.eve_bases = [random.choice(list(Basis)) for _ in range(len(qubits))]
            self.eve_measurements = []
        
        for i, qubit in enumerate(qubits):
            # Copia del qubit original
            transmitted = Qubit(qubit.bit_value, qubit.basis)
            
            # Si Eve está presente, intercepta y mide el qubit
            if self.eve_present:
                eve_measurement = qubit.measure(self.eve_bases[i])
                self.eve_measurements.append(eve_measurement)
                
                # Eve reenvía un nuevo qubit basado en su medición
                transmitted = Qubit(eve_measurement, self.eve_bases[i])
            
            # Simular errores del canal
            if random.random() < self.error_rate:
                # Invertir el bit con probabilidad error_rate
                transmitted.bit_value = 1 - transmitted.bit_value
            
            transmitted_qubits.append(transmitted)
        
        return transmitted_qubits

class BB84Protocol:
    def __init__(self, num_bits=1000, error_rate=0.0, eve_present=False):
        """
        Inicializa el protocolo BB84
        
        Args:
            num_bits (int): Número de bits a transmitir
            error_rate (float): Tasa de error del canal
            eve_present (bool): Si hay un espía en el canal
        """
        self.num_bits = num_bits
        self.channel = QuantumChannel(error_rate, eve_present)
        
        # Datos de Alice
        self.alice_bits = []
        self.alice_bases = []
        
        # Datos de Bob
        self.bob_bases = []
        self.bob_measurements = []
        
        # Datos de Eve (si está presente)
        self.eve_bases = []
        self.eve_measurements = []
        
        # Resultados del protocolo
        self.matching_bases_indices = []
        self.sample_indices = []
        self.key_indices = []
        self.error_rate = 0.0
        self.final_key = []
    
    def alice_prepares_qubits(self):
        """Alice prepara qubits aleatorios en bases aleatorias"""
        self.alice_bits = [random.randint(0, 1) for _ in range(self.num_bits)]
        self.alice_bases = [random.choice(list(Basis)) for _ in range(self.num_bits)]
        
        qubits = [Qubit(self.alice_bits[i], self.alice_bases[i]) 
                 for i in range(self.num_bits)]
        
        return qubits
    
    def bob_measures_qubits(self, qubits):
        """Bob mide los qubits recibidos en bases aleatorias"""
        self.bob_bases = [random.choice(list(Basis)) for _ in range(len(qubits))]
        self.bob_measurements = [qubits[i].measure(self.bob_bases[i]) 
                               for i in range(len(qubits))]
    
    def compare_bases(self):
        """Alice y Bob comparan sus bases y descartan las no coincidentes"""
        self.matching_bases_indices = [i for i in range(self.num_bits) 
                                     if self.alice_bases[i] == self.bob_bases[i]]
        
        print(f"Bases coincidentes: {len(self.matching_bases_indices)}/{self.num_bits}")
    
    def estimate_error_rate(self, sample_size=100):
        """
        Alice y Bob estiman la tasa de error sacrificando algunos bits
        
        Args:
            sample_size (int): Número de bits a sacrificar para la estimación
        """
        # Asegurarse de que no intentamos muestrear más bits de los disponibles
        sample_size = min(sample_size, len(self.matching_bases_indices))
        
        # Seleccionar índices aleatorios para el muestreo
        self.sample_indices = random.sample(self.matching_bases_indices, sample_size)
        
        # Contar errores en los bits de muestra
        errors = sum(1 for i in self.sample_indices 
                    if self.alice_bits[i] != self.bob_measurements[i])
        
        self.error_rate = errors / sample_size
        
        print(f"Tasa de error estimada: {self.error_rate:.2%}")
        
        # Determinar si la tasa de error es aceptable (típicamente < 11%)
        return self.error_rate < 0.11
    
    def generate_key(self):
        """Alice y Bob generan la clave final con los bits restantes"""
        # Excluir los bits usados para la estimación de error
        self.key_indices = [i for i in self.matching_bases_indices 
                          if i not in self.sample_indices]
        
        # Extraer los bits para la clave final
        self.final_key = [self.alice_bits[i] for i in self.key_indices]
        
        # En una implementación real, se aplicaría:
        # 1. Reconciliación de información (corrección de errores)
        # 2. Amplificación de privacidad (hash)
        
        # Simulamos la amplificación de privacidad con un hash simple
        key_string = ''.join(str(bit) for bit in self.final_key)
        hashed_key = hashlib.sha256(key_string.encode()).hexdigest()
        
        print(f"Longitud de la clave final: {len(self.final_key)} bits")
        print(f"Muestra de la clave: {''.join(str(bit) for bit in self.final_key[:16])}...")
        print(f"Clave hash: {hashed_key}")
        
        return self.final_key
    
    def run_protocol(self):
        """Ejecuta el protocolo BB84 completo"""
        print("=== Iniciando Protocolo BB84 ===")
        
        # Paso 1: Alice prepara qubits
        print("\nPaso 1: Alice prepara qubits")
        qubits = self.alice_prepares_qubits()
        
        # Paso 2: Alice envía qubits a Bob a través del canal cuántico
        print("\nPaso 2: Transmisión por canal cuántico")
        received_qubits = self.channel.transmit(qubits)
        
        # Si Eve está presente, guardamos sus datos
        if self.channel.eve_present:
            self.eve_bases = self.channel.eve_bases
            self.eve_measurements = self.channel.eve_measurements
        
        # Paso 3: Bob mide los qubits recibidos
        print("\nPaso 3: Bob mide los qubits recibidos")
        self.bob_measures_qubits(received_qubits)
        
        # Paso 4: Alice y Bob comparan bases
        print("\nPaso 4: Comparación de bases")
        self.compare_bases()
        
        # Paso 5: Estimación de la tasa de error
        print("\nPaso 5: Estimación de la tasa de error")
        error_acceptable = self.estimate_error_rate()
        
        if not error_acceptable:
            print("\n⚠️ Tasa de error demasiado alta. Posible presencia de espía.")
            print("Protocolo abortado.")
            return None
        
        # Paso 6: Generación de la clave final
        print("\nPaso 6: Generación de la clave final")
        key = self.generate_key()
        
        print("\n=== Protocolo BB84 completado con éxito ===")
        
        return key

# Ejemplo de uso
if __name__ == "__main__":
    # Ejecutar protocolo sin espía
    print("\n=== EJECUCIÓN SIN ESPÍA ===")
    protocol = BB84Protocol(num_bits=1000, error_rate=0.05, eve_present=False)
    key_without_eve = protocol.run_protocol()
    
    # Ejecutar protocolo con espía
    print("\n\n=== EJECUCIÓN CON ESPÍA ===")
    protocol_with_eve = BB84Protocol(num_bits=1000, error_rate=0.05, eve_present=True)
    key_with_eve = protocol_with_eve.run_protocol()

Explicación del Protocolo BB84

Fundamentos Cuánticos

El protocolo BB84 se basa en dos principios fundamentales de la mecánica cuántica:

  1. Principio de incertidumbre de Heisenberg: No es posible medir simultáneamente ciertas propiedades complementarias de un sistema cuántico con precisión arbitraria.
  2. Teorema de no clonación: Es imposible crear una copia exacta de un estado cuántico desconocido.

Estos principios garantizan que cualquier intento de interceptar la comunicación cuántica alterará inevitablemente los estados, permitiendo detectar la presencia de un espía.

Pasos del Protocolo BB84

Paso 1: Preparación de Qubits

Alice genera dos secuencias aleatorias:

  • Una secuencia de bits (0s y 1s)
  • Una secuencia de bases (rectilinear + y diagonal ×)

Para cada bit, Alice prepara un qubit en la base correspondiente:

  • Base rectilinear (+): |0⟩ para bit 0, |1⟩ para bit 1
  • Base diagonal (×): |+⟩ para bit 0, |−⟩ para bit 1

Paso 2: Transmisión Cuántica

Alice envía los qubits a Bob a través de un canal cuántico.

Si Eve intenta interceptar los qubits, debe elegir una base para medirlos:

  • Si elige la misma base que Alice, obtiene el bit correcto pero altera el estado
  • Si elige una base diferente, obtiene un resultado aleatorio y altera el estado

Paso 3: Medición de Qubits

Bob genera una secuencia aleatoria de bases y mide cada qubit recibido en la base correspondiente.

Resultados de la medición:

  • Si Bob usa la misma base que Alice, obtiene el bit original con alta probabilidad
  • Si Bob usa una base diferente, obtiene un resultado aleatorio (0 o 1 con igual probabilidad)

Paso 4: Comparación de Bases

Alice y Bob revelan públicamente las bases que utilizaron (pero no los bits).

Descartan todos los bits donde usaron bases diferentes, conservando solo aquellos donde coincidieron las bases.

Aproximadamente el 50% de los bits se conservan en este paso.

Paso 5: Estimación de Error

Alice y Bob revelan públicamente una muestra aleatoria de sus bits coincidentes para estimar la tasa de error.

Si la tasa de error es mayor que un umbral (típicamente 11%), sospechan la presencia de un espía y abortan el protocolo.

La presencia de Eve introduce errores porque:

  • Eve elige la base incorrecta aproximadamente el 50% del tiempo
  • Cuando Eve elige la base incorrecta, introduce un error del 50%
  • Esto resulta en una tasa de error del 25% cuando Eve intercepta todos los qubits

Paso 6: Procesamiento Final

Si la tasa de error es aceptable, Alice y Bob:

  • Realizan reconciliación de información (corrección de errores)
  • Aplican amplificación de privacidad (funciones hash) para eliminar cualquier información parcial que Eve pudiera haber obtenido

El resultado es una clave secreta compartida que puede usarse para cifrado simétrico.

QKD vs. Criptografía Post-Cuántica

Característica Distribución Cuántica de Claves (QKD) Criptografía Post-Cuántica (PQC)
Base de seguridad Leyes de la física cuántica Problemas matemáticos computacionalmente difíciles
Hardware requerido Dispositivos cuánticos especializados Hardware convencional
Distancia Limitada (típicamente <100 km sin repetidores) Ilimitada
Tasa de transferencia Baja (kbps o menos) Alta (comparable a criptografía clásica)
Infraestructura Requiere canales cuánticos dedicados Compatible con infraestructura existente
Aplicación Solo distribución de claves Cifrado, firmas, intercambio de claves
Madurez Implementaciones comerciales limitadas Estándares emergentes (NIST)
Seguridad probada Seguridad incondicional (teóricamente) Seguridad computacional (basada en supuestos)

Aplicaciones Prácticas de QKD

A pesar de sus limitaciones, QKD ya se está utilizando en varios contextos:

El futuro de QKD incluye el desarrollo de repetidores cuánticos y redes cuánticas que podrían extender significativamente su alcance y aplicabilidad.

Demostración Interactiva: Protocolo BB84

Estado

Esperando inicio de simulación...

Alice

Canal Cuántico

Bob

Canal Clásico

Clave Final