Guía de Laboratorio: Simulación del Algoritmo de Shor
Objetivos de Aprendizaje
- Comprender los fundamentos teóricos del algoritmo cuántico de Shor
- Implementar una simulación simplificada del algoritmo
- Analizar cómo este algoritmo compromete la seguridad de RSA
- Evaluar las implicaciones para la criptografía actual
Requisitos Previos
- Conocimientos básicos de criptografía de clave pública (RSA)
- Familiaridad con conceptos matemáticos: teoría de números, aritmética modular
- Python 3.8 o superior instalado
- Bibliotecas requeridas: numpy, matplotlib, qiskit (opcional para la parte avanzada)
Nota: Esta práctica utiliza una simulación clásica del algoritmo de Shor. No se requiere un ordenador cuántico real.
Introducción Teórica
El algoritmo de Shor, desarrollado por Peter Shor en 1994, es un algoritmo cuántico que puede factorizar números enteros en tiempo polinómico. Su importancia radica en que puede romper eficientemente el cifrado RSA, que basa su seguridad en la dificultad de factorizar números grandes.
El algoritmo consta de dos partes principales:
- Reducción del problema de factorización a encontrar el período de una función: Transformar el problema de factorizar N en encontrar el período de la función f(x) = a^x mod N, donde a es un número coprimo con N.
- Encontrar el período utilizando la transformada cuántica de Fourier: Utilizar un algoritmo cuántico para encontrar el período de la función anterior de manera eficiente.
Una vez encontrado el período r, podemos calcular factores de N con alta probabilidad utilizando el máximo común divisor (MCD) de N y a^(r/2) ± 1.
Parte 1: Implementación Clásica Simplificada
1.1 Configuración del Entorno
Crea un nuevo archivo Python llamado shor_simulation.py e importa las bibliotecas necesarias:
import numpy as np
import math
import random
import matplotlib.pyplot as plt
from fractions import Fraction
1.2 Implementación de Funciones Auxiliares
Primero, implementaremos algunas funciones auxiliares necesarias:
# Calcular el máximo común divisor (MCD)
def gcd(a, b):
while b:
a, b = b, a % b
return a
# Exponenciación modular rápida
def mod_exp(base, exponent, modulus):
if modulus == 1:
return 0
result = 1
base = base % modulus
while exponent > 0:
if exponent % 2 == 1:
result = (result * base) % modulus
exponent = exponent >> 1
base = (base * base) % modulus
return result
# Encontrar un número aleatorio coprimo con n
def find_coprime(n):
a = random.randint(2, n-1)
while gcd(a, n) != 1:
a = random.randint(2, n-1)
return a
# Expansión de fracción continua para aproximar fracciones
def continued_fraction_expansion(x, y, max_denominator):
fractions = []
a = Fraction(x, y).limit_denominator(max_denominator)
fractions.append(a)
# Generar convergentes intermedios
n, d = a.numerator, a.denominator
for i in range(1, d):
if i * a.denominator <= max_denominator:
fractions.append(Fraction(round(i * a.numerator / a.denominator), i))
return fractions
1.3 Implementación del Algoritmo de Shor Clásico
Ahora implementaremos una versión simplificada del algoritmo de Shor:
def find_period_classically(a, N):
"""
Encuentra el período de f(x) = a^x mod N de manera clásica (ineficiente).
En un ordenador cuántico real, esta parte sería mucho más eficiente.
"""
x = 1
for r in range(1, N):
x = (x * a) % N
if x == 1:
return r
return None
def shor_algorithm_classical(N, max_attempts=5):
"""
Implementación clásica simplificada del algoritmo de Shor
"""
if N % 2 == 0:
return 2, N // 2
for _ in range(max_attempts):
# Paso 1: Elegir un número aleatorio a < N
a = find_coprime(N)
print(f"Intentando con a = {a}")
# Paso 2: Encontrar el período r (en un ordenador cuántico, esto sería mucho más rápido)
r = find_period_classically(a, N)
if r is None:
print("No se encontró período. Intentando con otro valor de a.")
continue
print(f"Período encontrado: r = {r}")
# Paso 3: Verificar condiciones
if r % 2 != 0:
print("El período es impar. Intentando con otro valor de a.")
continue
x = mod_exp(a, r // 2, N)
if x == N - 1:
print("a^(r/2) ≡ -1 (mod N). Intentando con otro valor de a.")
continue
# Paso 4: Calcular factores
factor1 = gcd(x - 1, N)
factor2 = gcd(x + 1, N)
if factor1 > 1 and factor1 < N:
return factor1, N // factor1
if factor2 > 1 and factor2 < N:
return factor2, N // factor2
return None, None
1.4 Función Principal y Visualización
Finalmente, implementaremos la función principal y algunas visualizaciones:
def main():
# Número a factorizar (pequeño para esta simulación)
N = 15 # = 3 × 5
print(f"Intentando factorizar N = {N} usando el algoritmo de Shor (simulación clásica)")
# Ejecutar el algoritmo
factor1, factor2 = shor_algorithm_classical(N)
if factor1 is not None:
print(f"¡Factorización exitosa! {N} = {factor1} × {factor2}")
# Visualización
plt.figure(figsize=(10, 6))
# Gráfico de la función f(x) = a^x mod N
a = find_coprime(N)
x_values = list(range(20))
y_values = [mod_exp(a, x, N) for x in x_values]
plt.subplot(1, 2, 1)
plt.plot(x_values, y_values, 'o-')
plt.title(f"Función f(x) = {a}^x mod {N}")
plt.xlabel("x")
plt.ylabel(f"{a}^x mod {N}")
plt.grid(True)
# Histograma de valores
plt.subplot(1, 2, 2)
plt.hist(y_values, bins=range(min(y_values), max(y_values) + 2), alpha=0.7)
plt.title(f"Distribución de valores de {a}^x mod {N}")
plt.xlabel("Valor")
plt.ylabel("Frecuencia")
plt.grid(True)
plt.tight_layout()
plt.savefig('shor_visualization.png')
plt.show()
else:
print("No se pudo factorizar N con los intentos realizados.")
if __name__ == "__main__":
main()
Parte 2: Análisis de Seguridad de RSA
2.1 Implementación Simplificada de RSA
Ahora implementaremos una versión simplificada de RSA para demostrar su vulnerabilidad:
# Crear un nuevo archivo rsa_simulation.py
import random
from math import gcd
def is_prime(n, k=5):
"""Test de primalidad de Miller-Rabin simplificado"""
if n <= 1 or n == 4:
return False
if n <= 3:
return True
# Implementación simplificada para números pequeños
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
def generate_prime(bits):
"""Genera un número primo de aproximadamente 'bits' bits"""
# Para esta simulación, usamos primos pequeños
primes = [p for p in range(10, 100) if is_prime(p)]
return random.choice(primes)
def mod_inverse(e, phi):
"""Calcula el inverso modular de e módulo phi"""
def extended_gcd(a, b):
if a == 0:
return b, 0, 1
else:
gcd, x, y = extended_gcd(b % a, a)
return gcd, y - (b // a) * x, x
g, x, y = extended_gcd(e, phi)
if g != 1:
raise Exception('El inverso modular no existe')
else:
return x % phi
def generate_rsa_keys():
"""Genera un par de claves RSA (simplificado)"""
# Generar dos primos distintos
p = generate_prime(8)
q = generate_prime(8)
while p == q:
q = generate_prime(8)
n = p * q
phi = (p - 1) * (q - 1)
# Elegir e coprimo con phi
e = random.randint(3, phi - 1)
while gcd(e, phi) != 1:
e = random.randint(3, phi - 1)
# Calcular d (inverso modular de e módulo phi)
d = mod_inverse(e, phi)
return (e, n), (d, n), p, q
def rsa_encrypt(message, public_key):
"""Cifra un mensaje usando RSA"""
e, n = public_key
return pow(message, e, n)
def rsa_decrypt(ciphertext, private_key):
"""Descifra un mensaje usando RSA"""
d, n = private_key
return pow(ciphertext, d, n)
def main_rsa():
# Generar claves
public_key, private_key, p, q = generate_rsa_keys()
e, n = public_key
d, _ = private_key
print(f"Generadas claves RSA con n = {n} = {p} × {q}")
print(f"Clave pública: (e={e}, n={n})")
print(f"Clave privada: (d={d}, n={n})")
# Cifrar y descifrar un mensaje
message = random.randint(2, n-1)
print(f"Mensaje original: {message}")
ciphertext = rsa_encrypt(message, public_key)
print(f"Mensaje cifrado: {ciphertext}")
decrypted = rsa_decrypt(ciphertext, private_key)
print(f"Mensaje descifrado: {decrypted}")
# Intentar romper RSA usando el algoritmo de Shor
print("\nIntentando romper RSA usando el algoritmo de Shor...")
from shor_simulation import shor_algorithm_classical
factor1, factor2 = shor_algorithm_classical(n)
if factor1 is not None:
print(f"¡RSA comprometido! Se han encontrado los factores: {factor1} y {factor2}")
# Reconstruir la clave privada
phi_recovered = (factor1 - 1) * (factor2 - 1)
d_recovered = mod_inverse(e, phi_recovered)
print(f"Clave privada recuperada: (d={d_recovered}, n={n})")
# Verificar descifrando el mensaje
decrypted_recovered = pow(ciphertext, d_recovered, n)
print(f"Mensaje descifrado con la clave recuperada: {decrypted_recovered}")
print(f"¿Coincide con el mensaje original? {'Sí' if decrypted_recovered == message else 'No'}")
else:
print("No se pudo factorizar n con los intentos realizados.")
if __name__ == "__main__":
main_rsa()
Parte 3: Ejercicios y Preguntas de Reflexión
3.1 Ejercicios
- Modifica el código para factorizar otros números compuestos pequeños (por ejemplo, 21, 33, 35).
- Implementa una función que calcule el tiempo que tardaría un ordenador clásico en factorizar números de diferentes tamaños usando el algoritmo de fuerza bruta.
- Compara la complejidad teórica del algoritmo de Shor (O(log³ N)) con la del mejor algoritmo clásico conocido (aproximadamente O(e^(log N)^(1/3) * (log log N)^(2/3))).
- Modifica el código RSA para usar números primos más grandes (dentro de lo razonable para una simulación) y analiza cómo afecta esto al tiempo de ejecución del algoritmo de Shor clásico.
3.2 Preguntas de Reflexión
- ¿Por qué el algoritmo de Shor representa una amenaza tan significativa para RSA?
- ¿Qué tamaño de clave RSA se considera seguro actualmente contra ordenadores clásicos? ¿Y contra ordenadores cuánticos potenciales?
- ¿Qué otros algoritmos criptográficos, además de RSA, son vulnerables al algoritmo de Shor?
- ¿Cuáles son las limitaciones actuales para implementar el algoritmo de Shor en ordenadores cuánticos reales?
- ¿Qué estrategias de migración recomendarías a una organización que actualmente utiliza RSA para proteger información sensible a largo plazo?
Parte 4: Extensión (Opcional) - Simulación Cuántica
Si tienes experiencia con bibliotecas de computación cuántica como Qiskit, puedes intentar implementar una simulación más realista del algoritmo de Shor:
# Requiere: pip install qiskit
from qiskit import QuantumCircuit, Aer, execute
from qiskit.visualization import plot_histogram
import numpy as np
import matplotlib.pyplot as plt
# Esta es una implementación muy simplificada y parcial
# Para una implementación completa, consulta la documentación de Qiskit
def qft_rotations(circuit, n):
"""Rotaciones QFT sin swaps"""
if n == 0:
return circuit
n -= 1
circuit.h(n)
for qubit in range(n):
circuit.cp(np.pi/2**(n-qubit), qubit, n)
qft_rotations(circuit, n)
def qft(circuit, n):
"""Transformada cuántica de Fourier"""
qft_rotations(circuit, n)
# Swaps
for qubit in range(n//2):
circuit.swap(qubit, n-qubit-1)
return circuit
def inverse_qft(circuit, n):
"""QFT inversa"""
# Swaps
for qubit in range(n//2):
circuit.swap(qubit, n-qubit-1)
# Rotaciones inversas
for j in range(n):
for m in range(j):
circuit.cp(-np.pi/2**(j-m), m, j)
circuit.h(j)
return circuit
def shor_period_finding_circuit(a, N, n_count, n_target):
"""Circuito para encontrar el período en el algoritmo de Shor"""
# Crear circuito
circuit = QuantumCircuit(n_count + n_target, n_count)
# Inicializar registros
for q in range(n_count):
circuit.h(q)
circuit.x(n_count)
# Aplicar operaciones U controladas
# (Esta es una simplificación extrema)
for q in range(n_count):
# Aplicar a^(2^q) mod N controlado por q
# En una implementación real, esto sería mucho más complejo
circuit.cp(2*np.pi*a**(2**q) % N / N, q, n_count)
# Aplicar QFT inversa
inverse_qft(circuit, n_count)
# Medir
circuit.measure(range(n_count), range(n_count))
return circuit
def simulate_shor_quantum(a, N):
"""Simula la parte cuántica del algoritmo de Shor"""
# Parámetros
n_count = 8 # Número de qubits para el registro de conteo
n_target = 4 # Número de qubits para el registro objetivo
# Crear circuito
circuit = shor_period_finding_circuit(a, N, n_count, n_target)
# Simular
simulator = Aer.get_backend('qasm_simulator')
result = execute(circuit, simulator, shots=1024).result()
counts = result.get_counts()
# Visualizar
plot_histogram(counts)
plt.title(f"Distribución de mediciones para a={a}, N={N}")
plt.savefig('shor_quantum_simulation.png')
plt.show()
return counts
# Ejemplo de uso
# simulate_shor_quantum(7, 15)
Esta implementación es muy simplificada y no funcionará correctamente para el algoritmo de Shor completo, pero proporciona una idea de cómo se podría abordar usando Qiskit.
Entregables
Al finalizar esta práctica, deberás entregar:
- Código fuente de las implementaciones (
shor_simulation.pyyrsa_simulation.py) - Capturas de pantalla o gráficos generados durante la ejecución
- Un informe breve (máximo 2 páginas) que incluya:
- Resultados obtenidos en las factorizaciones
- Respuestas a las preguntas de reflexión
- Análisis de la vulnerabilidad de RSA frente al algoritmo de Shor
- Conclusiones sobre las implicaciones para la seguridad criptográfica actual