GhostAcademy
GhostAcademy
Criptografía Asimétrica — Teoría y Práctica
Guía Completa
Criptografía Asimétrica
Clave Pública y Clave Privada

La criptografía asimétrica es la tecnología que hace posible Internet seguro: HTTPS, SSH, las firmas digitales de tu DNI electrónico, los certificados de los bancos y las claves GPG. Se basa en un principio matemático elegante: dos claves distintas pero relacionadas matemáticamente, donde lo que cifra una solo puede descifrarlo la otra.

La idea central en una analogía

📬 Analogía del buzón con cerradura
🔓 Clave pública
La cerradura
→
La publicas al mundo entero
→
Cualquiera puede
echar un mensaje
🔑 Clave privada
La llave
→
Solo tú la tienes — NUNCA se comparte
→
Solo tú puedes
abrir el buzón

Imagina un buzón con una ranura y una cerradura. Cualquier persona puede meter un sobre por la ranura (usando tu clave pública), pero solo el dueño de la llave (clave privada) puede abrir el buzón y leer los sobres. La clave pública cifra; la clave privada descifra.

¿Dónde usas criptografía asimétrica cada día?

TecnologíaAlgoritmo usadoPara qué lo usa
HTTPS (candado del navegador)RSA o ECDSA + ECDHAutenticar el servidor y negociar la clave de sesión
SSH (acceso remoto a servidores)RSA, Ed25519, ECDSAAutenticar sin contraseña mediante par de claves
GPG / Email cifradoRSA o ElGamalCifrar emails y firmarlos digitalmente
DNI electrónicoRSA-2048Firma digital con validez legal
Bitcoin / BlockchainECDSA (secp256k1)Demostrar propiedad de una dirección sin revelar la clave
JWT (tokens de autenticación web)RSA o ECDSAFirmar tokens para que no puedan falsificarse
Signal / WhatsAppCurve25519 (DH)Cifrado de extremo a extremo con forward secrecy
Fundamentos — 01
⚖️ Simétrica vs Asimétrica

Existen dos grandes familias de criptografía. Entender las diferencias explica por qué la asimétrica no ha reemplazado a la simétrica — sino que se usan juntas.

CaracterísticaSimétrica (AES, ChaCha20)Asimétrica (RSA, ECC)
Número de clavesUna sola clave compartidaPar de claves: pública + privada
Problema fundamental¿Cómo compartir la clave de forma segura?No hay problema de distribución — la pública es pública
VelocidadMuy rápida (hardware dedicado)Lenta (operaciones matemáticas costosas)
Longitud de clave128-256 bits es suficiente2048-4096 bits para RSA; 256-521 bits para ECC
Cifrar datos grandesIdealNo práctico (lento y limita tamaño)
Firmas digitalesNo directamenteFunción nativa
Sin contacto previoImposible sin canal seguroPosible — solo necesitas la clave pública
💡 Por qué se usan juntas — cifrado híbridoEn la práctica, la asimétrica no cifra los datos directamente. Cifra una clave simétrica pequeña. Luego esa clave simétrica cifra los datos. Esto combina lo mejor de ambas: la distribución segura de claves de la asimétrica + la velocidad de la simétrica. Así funciona TLS/HTTPS.
🔄 Cifrado híbrido en la práctica (TLS)
Cliente
→
Genera clave
AES temporal
→
Cifra la clave AES
con RSA pública del servidor
→
Servidor descifra
con RSA privada
↓
Cliente
⇄
Todos los datos
cifrados con AES
(rápido)
⇄
Servidor
Fundamentos — 02
🗝 Clave Pública y Clave Privada

El par de claves es el concepto central de toda la criptografía asimétrica. Entender qué hace cada una, qué nunca debe hacer cada una, y cómo se relacionan matemáticamente.

Las dos claves y sus roles

🔓 CLAVE PÚBLICA — Se comparte libremente
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2a2rwplBQLF29amygykE
MmYz0+Kcj3bKBp29SkH1lMiE0VQT1p0YhJDrqEFqGZi5BO1QQWPGB7FcjfxGhL
-----END PUBLIC KEY-----

✓ Publicar en servidores de claves   ✓ Incluir en emails   ✓ Poner en tu web   ✓ Compartir con todo el mundo
🔑 CLAVE PRIVADA — NUNCA se comparte
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZravCmUFAsXb1
qbKDKQQyZjPT4pyPdsoGnb1KQfWUyITRVBPWnRiEkOuoQWoZmLkE7VBBY8YHsV
-----END PRIVATE KEY-----

✗ Nunca enviar por email   ✗ Nunca subir a GitHub   ✗ Nunca compartir con nadie
✓ Guardar cifrada con passphrase   ✓ Backup seguro y offline

Las cuatro operaciones fundamentales

OperaciónQuién usa qué clavePara qué sirveEjemplo
CifrarRemitente usa la clave pública del destinatarioSolo el destinatario puede leerloAlice cifra un mensaje con la clave pública de Bob
DescifrarDestinatario usa su clave privadaRecuperar el mensaje originalBob descifra con su clave privada
FirmarFirmante usa su clave privadaDemostrar autoría e integridadAlice firma un contrato con su clave privada
Verificar firmaCualquiera usa la clave pública del firmanteConfirmar que fue firmado por el dueñoBob verifica la firma con la clave pública de Alice
🔑 La propiedad fundamentalEl par de claves está relacionado matemáticamente pero no es posible derivar la clave privada a partir de la pública en tiempo razonable. Esta asimetría computacional es el fundamento de toda la seguridad del sistema.
RSA — Teoría
📐 RSA — Teoría y Matemáticas

RSA (Rivest-Shamir-Adleman, 1977) es el algoritmo de clave pública más conocido. Su seguridad se basa en la dificultad matemática de factorizar el producto de dos números primos grandes. Entender las matemáticas desmitifica el algoritmo.

El problema difícil detrás de RSA

Multiplicar dos números primos es trivial. Pero dado su producto, encontrar de vuelta los dos primos originales es computacionalmente inviable para números grandes.

Factorización — fácil con números pequeños, imposible con números grandes
p × q = 61 × 53 = 3.233 ← fácil de factorizar
p × q = numero_de_617_digitos ← factorizar tarda más que la edad del universo
La clave RSA-2048 usa dos primos de ~1024 bits cada uno. Su producto tiene 2048 bits.

Generación de claves RSA — los 5 pasos

1
Elegir dos números primos grandes p y q
En la práctica se generan aleatoriamente y se verifica que son primos. Para el ejemplo usaremos p=61, q=53 (números pequeños para que los cálculos sean seguibles).
Paso 1 — Primos del ejemplo
p = 61
q = 53
2
Calcular n (el módulo) y φ(n) (la función de Euler)
n es el producto de p×q. φ(n) = (p-1)×(q-1) es la cantidad de números menores que n que no tienen factores comunes con n.
Paso 2 — Módulo y Phi
n = p × q = 61 × 53 = 3.233
φ(n) = (p-1) × (q-1) = 60 × 52 = 3.120
3
Elegir el exponente público e
e debe ser: (1) mayor que 1 y menor que φ(n), (2) coprimo con φ(n) — es decir, mcd(e, φ(n)) = 1. El valor 65537 es el más usado en RSA real porque es primo y tiene buenas propiedades computacionales.
Paso 3 — Exponente público e
e = 17 ← elegimos 17 (es coprimo con 3120)
mcd(17, 3120) = 1 ← correcto: no comparten factores
4
Calcular el exponente privado d
d es el inverso modular de e respecto a φ(n). Es decir, d×e ≡ 1 (mod φ(n)). Se calcula con el algoritmo extendido de Euclides. Este valor d es secreto — es el núcleo de la clave privada.
Paso 4 — Exponente privado d (inverso modular)
d × e ≡ 1 (mod φ(n))
d × 17 ≡ 1 (mod 3120)
d = 2753 ← porque 2753 × 17 = 46801 = 15 × 3120 + 1
5
Las claves están listas
La clave pública se compone de (n, e). La clave privada se compone de (n, d). Los valores p, q y φ(n) se destruyen tras la generación — si el atacante los conociera, podría derivar d.
Resumen — Par de claves RSA del ejemplo
Clave PÚBLICA: (n=3233, e=17)
Clave PRIVADA: (n=3233, d=2753)
En RSA-2048 real: n tiene 617 dígitos decimales (2048 bits)
RSA — Ejemplo
🔢 RSA — Ejemplo con Números Reales

Usando el par de claves generado en la sección anterior (n=3233, e=17, d=2753), vamos a cifrar y descifrar un mensaje real paso a paso.

Cifrar con la clave pública

Para cifrar un número M (el mensaje), aplicamos la fórmula: C = Me mod n. El resultado C es el texto cifrado.

Cifrado RSA — C = M^e mod n
M = 65 ← mensaje: el número 65 (en la práctica sería un bloque de bytes)
e = 17 ← exponente público
n = 3.233 ← módulo
C = 65^17 mod 3.233 = 2790
El texto cifrado es: 2790. Enviamos 2790 a Bob. Sin su clave privada, no hay forma de saber que el original era 65.

Descifrar con la clave privada

Para descifrar C con la clave privada, aplicamos: M = Cd mod n. La magia matemática del teorema de Euler garantiza que esto devuelve M exactamente.

Descifrado RSA — M = C^d mod n
C = 2790 ← texto cifrado recibido
d = 2753 ← exponente privado (secreto)
n = 3.233 ← módulo
M = 2790^2753 mod 3.233 = 65
¡El mensaje original era 65! ✓

Verificar con Python

Python — RSA manual con los números del ejemplo
# RSA desde cero con los parámetros del ejemplo
# Puedes ejecutar esto en cualquier Python 3

## PASO 1: Definir los primos
p, q = 61, 53

## PASO 2: Calcular n y phi(n)
n = p * q          # 3233
phi = (p-1) * (q-1)# 3120

## PASO 3: Clave pública (e coprimo con phi)
e = 17
from math import gcd
assert gcd(e, phi) == 1, "e no es coprimo con phi"

## PASO 4: Clave privada (d inverso modular de e)
d = pow(e, -1, phi)  # Python 3.8+ calcula el inverso modular directamente
print(f"Clave pública:  (n={n}, e={e})")
print(f"Clave privada:  (n={n}, d={d})")
Clave pública:  (n=3233, e=17)
Clave privada:  (n=3233, d=2753)

## CIFRAR: C = M^e mod n
M = 65               # mensaje
C = pow(M, e, n)     # pow(base, exp, mod) es eficiente para números grandes
print(f"Mensaje original:  {M}")
print(f"Mensaje cifrado:   {C}")
Mensaje original:  65
Mensaje cifrado:   2790

## DESCIFRAR: M = C^d mod n
M_dec = pow(C, d, n)
print(f"Mensaje descifrado: {M_dec}")
assert M_dec == M, "¡Error de descifrado!"
Mensaje descifrado: 65  ← ¡correcto!

## VERIFICAR que sin d no se puede descifrar
# Sin conocer p y q, encontrar d requiere factorizar n
# Para n=3233 es fácil, pero para RSA-2048 es computacionalmente imposible
★ Por qué funciona — Teorema de EulerEl teorema de Euler garantiza que para cualquier M coprimo con n: Mφ(n) ≡ 1 (mod n). Como e×d ≡ 1 (mod φ(n)), tenemos que Me×d = Mkφ(n)+1 = (Mφ(n))k × M ≡ M (mod n). Las operaciones de cifrado y descifrado se cancelan exactamente.
RSA — Práctica
💻 RSA con OpenSSL y Python

Dejar los números pequeños y trabajar con RSA real: generar un par de claves de 2048 bits, cifrar un mensaje y descifrarlo usando las herramientas estándar de la industria.

Con OpenSSL — La herramienta estándar

BASH — RSA completo con OpenSSL
## PASO 1: Generar la clave privada RSA-2048
openssl genrsa -out clave_privada.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.........+++++
.....+++++
e is 65537 (0x10001)

## PASO 2: Extraer la clave pública de la privada
openssl rsa -in clave_privada.pem -pubout -out clave_publica.pem
writing RSA key

## Ver la clave pública generada
cat clave_publica.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2a2rwplBQLzskS6ACJM5
...
-----END PUBLIC KEY-----

## Ver los componentes matemáticos de la clave
openssl rsa -in clave_privada.pem -text -noout | head -20
Private-Key: (2048 bit, 2 primes)
modulus:
    00:d9:ad:ab:c2:99:41:40:bc:...   ← n (2048 bits)
publicExponent: 65537 (0x10001)      ← e = 65537
privateExponent:                     ← d (secreto)
    ...
prime1:                              ← p
prime2:                              ← q

## PASO 3: Cifrar un mensaje con la clave pública
echo "Hola GhostAcademy, esto es un secreto!" > mensaje.txt
openssl rsautl -encrypt -inkey clave_publica.pem -pubin \
  -in mensaje.txt -out mensaje_cifrado.bin

## Ver el contenido cifrado (binario ilegible)
xxd mensaje_cifrado.bin | head -5
00000000: 3a8f d291 4c7a 2b88 e931 f450 a2b3 ...   ← ilegible sin la clave privada

## PASO 4: Descifrar con la clave privada
openssl rsautl -decrypt -inkey clave_privada.pem \
  -in mensaje_cifrado.bin -out mensaje_descifrado.txt
cat mensaje_descifrado.txt
Hola GhostAcademy, esto es un secreto!

## CIFRADO HÍBRIDO PRÁCTICO — RSA + AES (lo que se usa en producción)
# Cifrar un archivo grande (RSA tiene límite de tamaño)

# 1. Generar clave AES aleatoria
openssl rand -base64 32 > clave_aes.key

# 2. Cifrar el archivo grande con AES
openssl enc -aes-256-cbc -salt -in archivo_grande.txt \
  -out archivo_cifrado.enc -pass file:clave_aes.key

# 3. Cifrar la clave AES con RSA (esto es lo que se envía al destinatario)
openssl rsautl -encrypt -inkey clave_publica.pem -pubin \
  -in clave_aes.key -out clave_aes_cifrada.bin

# Enviar al destinatario: archivo_cifrado.enc + clave_aes_cifrada.bin
# El destinatario descifra la clave AES con su RSA privada, luego descifra el archivo

Con Python — cryptography library

Python — RSA con la librería cryptography
# Instalar: pip install cryptography
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization

## GENERAR PAR DE CLAVES RSA-2048
clave_privada = rsa.generate_private_key(
    public_exponent=65537,   # e estándar
    key_size=2048,           # tamaño de n en bits
)
clave_publica = clave_privada.public_key()

## CIFRAR con la clave pública (OAEP es el padding recomendado)
mensaje = b"Hola GhostAcademy, esto es secreto!"

cifrado = clave_publica.encrypt(
    mensaje,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)
print(f"Mensaje cifrado: {cifrado.hex()[:60]}...")
Mensaje cifrado: 3a8fd2914c7a2b88e931f450a2b3c8d1e4f6a7b8c9d0e1f2...

## DESCIFRAR con la clave privada
descifrado = clave_privada.decrypt(
    cifrado,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)
print(f"Mensaje descifrado: {descifrado.decode()}")
Mensaje descifrado: Hola GhostAcademy, esto es secreto!

## EXPORTAR CLAVES en formato PEM
pem_privada = clave_privada.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.BestAvailableEncryption(b"mi_passphrase")
)

pem_publica = clave_publica.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

with open("privada.pem", "wb") as f: f.write(pem_privada)
with open("publica.pem", "wb") as f: f.write(pem_publica)
print("Claves guardadas en privada.pem y publica.pem")
Firmas Digitales — Teoría
✍️ Firmas Digitales — Cómo Funcionan

Las firmas digitales son la operación inversa al cifrado: si cifrar usa la clave pública del destinatario, firmar usa la clave privada propia. Esto permite probar autoría e integridad sin revelar el contenido.

¿Qué garantiza una firma digital?

PropiedadSignificadoCómo se consigue
AutenticidadEl documento fue creado por quien dice haberlo firmadoSolo el dueño de la clave privada puede crear la firma
IntegridadEl documento no fue modificado después de firmarseEl hash del documento forma parte de la firma
No repudioEl firmante no puede negar haber firmadoSolo él tenía la clave privada en el momento de la firma

El proceso completo de firma y verificación

✍️ Alice firma un contrato y lo envía a Bob
📄 Alice
tiene el documento
→
SHA-256
hash
→
Hash:
a3f8c2...
↓ cifra el hash con su clave PRIVADA
Firma digital
(hash cifrado)
+
Documento
original
→ envía →
Bob recibe
ambos
BOB VERIFICA:
Documento
recibido
→ SHA-256 →
Hash
calculado
→ ¿igual? →
Hash de
la firma
← descifra con clave PÚBLICA de Alice ←
✓ Si los hashes coinciden
→ firma válida
/
✗ Si no coinciden
→ documento alterado
💡 Por qué se firma el hash, no el documento completoFirmar directamente el documento con RSA sería lento (RSA es lento con datos grandes) y limitaría el tamaño. Al firmar el hash SHA-256 del documento (32 bytes siempre), la operación es rápida independientemente del tamaño del documento. El hash actúa como huella dactilar del contenido.

Firma RSA en números — usando nuestras claves del ejemplo

Firma y verificación con (n=3233, e=17, d=2753)
Supongamos que el hash del documento es H = 123
FIRMA (Alice usa su clave privada d):
S = H^d mod n = 123^2753 mod 3233 = 855
VERIFICACIÓN (Bob usa la clave pública e de Alice):
H' = S^e mod n = 855^17 mod 3233 = 123
H' == H → firma válida ✓
Firmas Digitales — Práctica
💻 Firmas Digitales — Práctica con OpenSSL

Firmar y verificar documentos reales usando OpenSSL y Python. Estas son las operaciones exactas que ocurren cuando se firman contratos digitales, commits de Git o paquetes de software.

BASH — Firmar y verificar con OpenSSL
## PREPARAR: reutilizamos las claves generadas antes
echo "Este contrato es vinculante. Fecha: $(date)" > contrato.txt
cat contrato.txt
Este contrato es vinculante. Fecha: Mon Jan 15 14:32:00 UTC 2024

## FIRMAR el documento con la clave privada
openssl dgst -sha256 -sign clave_privada.pem \
  -out contrato.sig contrato.txt

## Ver la firma (binario codificado en hex)
xxd contrato.sig | head -3
00000000: 7a3b c891 2e45 f7d8 9a12 ...   ← firma de 256 bytes (RSA-2048)

## VERIFICAR la firma con la clave pública
openssl dgst -sha256 -verify clave_publica.pem \
  -signature contrato.sig contrato.txt
Verified OK   ← firma válida

## QUÉ PASA si el documento es modificado después de firmar
echo "clausula_maliciosa" >> contrato.txt
openssl dgst -sha256 -verify clave_publica.pem \
  -signature contrato.sig contrato.txt
Verification Failure   ← ¡el documento fue alterado! la firma no coincide

## FIRMAR CON ECDSA (más eficiente que RSA para firmas)
openssl ecparam -name prime256v1 -genkey -noout -out ec_privada.pem
openssl ec -in ec_privada.pem -pubout -out ec_publica.pem
openssl dgst -sha256 -sign ec_privada.pem -out contrato_ec.sig contrato.txt
openssl dgst -sha256 -verify ec_publica.pem -signature contrato_ec.sig contrato.txt
Verified OK
Python — Firma digital completa
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization

## Generar par de claves
clave_privada = rsa.generate_private_key(public_exponent=65537, key_size=2048)
clave_publica = clave_privada.public_key()

## Documento a firmar
documento = b"Este contrato tiene validez legal. Firmado por: Alice"

## FIRMAR con la clave privada (PSS es el padding recomendado para firmas)
firma = clave_privada.sign(
    documento,
    padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    hashes.SHA256()
)
print(f"Firma generada: {firma.hex()[:60]}...")

## VERIFICAR con la clave pública
try:
    clave_publica.verify(
        firma,
        documento,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    print("✓ Firma válida — el documento es auténtico e íntegro")
except Exception as e:
    print(f"✗ Firma inválida: {e}")

## Qué ocurre con documento alterado
documento_alterado = b"Este contrato tiene validez legal. Firmado por: Bob"
try:
    clave_publica.verify(firma, documento_alterado,
        padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),
        hashes.SHA256())
except Exception:
    print("✗ Firma inválida — el documento fue modificado")
Firma generada: 7a3bc8912e45f7d89a12...
✓ Firma válida — el documento es auténtico e íntegro
✗ Firma inválida — el documento fue modificado
Algoritmos — ECC
📈 ECC — Criptografía de Curva Elíptica

Elliptic Curve Cryptography (ECC) es la alternativa moderna a RSA. Proporciona el mismo nivel de seguridad con claves mucho más cortas, lo que la hace más eficiente en dispositivos móviles, IoT y conexiones TLS modernas.

RSA vs ECC — Comparativa de tamaño de clave

Nivel de seguridadClave RSA necesariaClave ECC equivalenteRatio
80 bits1024 bits160 bits6:1
112 bits2048 bits224 bits9:1
128 bits3072 bits256 bits12:1
192 bits7680 bits384 bits20:1
256 bits15360 bits521 bits30:1

El problema difícil detrás de ECC

ECC se basa en el problema del logaritmo discreto en curvas elípticas. Una curva elíptica es el conjunto de puntos que satisfacen: y² = x³ + ax + b (mod p). Se puede "sumar" puntos de la curva de forma eficiente, pero dado un punto resultado P = n×G, encontrar n conociendo P y G es computacionalmente inviable.

Curva secp256k1 — la que usa Bitcoin
y² = x³ + 7 (mod p) ← a=0, b=7
p = 2²⁵⁶ − 2³² − 977 ← un primo de 256 bits
Clave privada: un número aleatorio k de 256 bits
Clave pública: P = k × G (punto en la curva)
Dado P y G, encontrar k es el "problema del logaritmo discreto en curvas elípticas" — computacionalmente imposible

Curvas elípticas más usadas

CurvaBitsDónde se usa
prime256v1 / P-256256TLS, ECDSA en certificados web, Android, Apple
secp384r1 / P-384384TLS de alta seguridad, gobierno, NSA Suite B
secp256k1256Bitcoin, Ethereum (no es la P-256 estándar de NIST)
Curve25519 / Ed25519255SSH moderno, Signal, WireGuard, TLS 1.3
X448448TLS 1.3 de alta seguridad
BASH / Python — ECC con OpenSSL y cryptography
## OPENSSL — Generar clave ECC (Ed25519 — la más moderna)
openssl genpkey -algorithm Ed25519 -out ed25519_privada.pem
openssl pkey -in ed25519_privada.pem -pubout -out ed25519_publica.pem

## Comparar tamaños con RSA
wc -c ed25519_privada.pem rsa_privada.pem
 119 ed25519_privada.pem    ← Ed25519: 119 bytes
1704 rsa_privada.pem        ← RSA-2048: 1704 bytes  ← 14x más grande

## PYTHON — ECC con Ed25519
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

privada = Ed25519PrivateKey.generate()
publica = privada.public_key()

mensaje = b"Firmado con curva elíptica Ed25519"
firma = privada.sign(mensaje)

# Verificar
publica.verify(firma, mensaje)
print("✓ Firma Ed25519 válida")

# Ed25519 es más rápido que RSA y más seguro a igual tamaño de clave
✓ Firma Ed25519 válida
Algoritmos — DH
🤝 Diffie-Hellman — Intercambio de Claves

Diffie-Hellman (1976) resolvió un problema fundamental: ¿cómo pueden dos personas acordar una clave secreta a través de un canal inseguro, sin haberse comunicado antes? Es el fundamento del "Perfect Forward Secrecy" en TLS moderno.

La analogía de los colores

La forma más intuitiva de entender DH: Alice y Bob mezclan colores en público, pero ningún observador puede saber el color secreto final aunque vea todos los intercambios.

🎨 DH con la analogía de los colores
Alice
Color público
AMARILLO
(g y p son públicos)
Bob
+ Color privado
ROJO (secreto)
→
→
+ Color privado
AZUL (secreto)
Alice envía:
NARANJA
(amarillo+rojo)
→ público →
Bob añade AZUL:
resultado MARRÓN
Alice añade ROJO:
resultado MARRÓN
← público ←
Bob envía:
VERDE
(amarillo+azul)
🔑 Secreto compartido: MARRÓN
=
🔑 Secreto compartido: MARRÓN
Eve ve: amarillo, naranja y verde — pero no puede saber que el secreto es MARRÓN sin conocer el rojo o el azul

Diffie-Hellman en matemáticas

DH con números pequeños (ejemplo didáctico)
Valores públicos acordados:
g = 5 (generador, público)    p = 23 (número primo, público)
Claves privadas (secretas):
a = 6 (secreto de Alice)    b = 15 (secreto de Bob)
Intercambio público:
A = g^a mod p = 5^6 mod 23 = 8 ← Alice envía esto
B = g^b mod p = 5^15 mod 23 = 19 ← Bob envía esto
Secreto compartido (cada uno lo calcula con el valor del otro):
Alice: B^a mod p = 19^6 mod 23 = 2
Bob: A^b mod p = 8^15 mod 23 = 2
¡Ambos obtienen el mismo secreto: 2! Sin haberse enviado ese valor ✓
Mundo Real — 01
🌐 TLS/HTTPS — Cómo usa la Criptografía Asimétrica

TLS (Transport Layer Security) es el protocolo detrás del candado verde del navegador. Combina criptografía asimétrica para autenticación e intercambio de claves, con criptografía simétrica para los datos. Entender TLS es entender la criptografía asimétrica en acción.

El TLS Handshake — paso a paso

🌐 TLS 1.3 Handshake simplificado
Cliente
(navegador)
→ ClientHello →
Servidor
(banco.com)
Versiones TLS, cipher suites, clave pública ECDH del cliente
Cliente
← ServerHello ←
Servidor
Certificado X.509 (clave pública RSA/ECDSA del servidor), clave pública ECDH del servidor
Verifica certificado
Calcula secreto DH
Deriva claves AES
→
Calcula secreto DH
Deriva claves AES
Cliente
⇄ Datos cifrados con AES ⇄
Servidor

Qué papel juega cada tipo de criptografía

Fase del handshakeAlgoritmoTipoPara qué
Verificar identidad del servidorRSA o ECDSAAsimétricaEl servidor firma con su clave privada; el cliente verifica con la pública del certificado
Intercambio de clave de sesiónECDH (Elliptic Curve DH)AsimétricaCliente y servidor derivan el mismo secreto sin enviarlo nunca — Perfect Forward Secrecy
Cifrar la comunicaciónAES-256-GCMSimétricaCifrado rápido de todos los datos HTTP con la clave derivada
Integridad del mensajeSHA-256/384 (HMAC)HashVerificar que ningún byte fue alterado en tránsito
💡 Perfect Forward Secrecy (PFS)En TLS 1.3, las claves de sesión se generan con ECDH y se descartan al terminar la sesión. Aunque un atacante grabe todo el tráfico HOY y consiga la clave privada del servidor MAÑANA, no podrá descifrar las sesiones pasadas — porque cada sesión usó claves efímeras únicas. TLS 1.2 sin ECDHE no tiene esta propiedad.
BASH — Inspeccionar el certificado TLS de un servidor
# Ver el certificado completo de un servidor
echo | openssl s_client -connect google.com:443 2>/dev/null | openssl x509 -noout -text | grep -E "Subject|Issuer|Algorithm|Public Key|Not|SANs"
Subject: CN=*.google.com
Issuer: C=US, O=Google Trust Services, CN=WR2
Signature Algorithm: ecdsa-with-SHA384
Public Key Algorithm: id-ecPublicKey (P-256)
Not Before: Jan 10 00:00:00 2024 GMT
Not After : Apr  3 23:59:59 2024 GMT

# Ver qué cipher suite se negoció
echo | openssl s_client -connect google.com:443 2>/dev/null | grep "Cipher"
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
← TLS 1.3 + AES-256-GCM para datos + SHA-384 para integridad

# Ver la clave pública del certificado
echo | openssl s_client -connect github.com:443 2>/dev/null | openssl x509 -noout -pubkey
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...   ← clave pública ECDSA P-256 de GitHub
-----END PUBLIC KEY-----
Mundo Real — 02
🔑 SSH y GPG — Práctica Completa

SSH y GPG son las dos herramientas de uso diario que más directamente exponen la criptografía asimétrica al usuario. SSH para autenticación sin contraseña en servidores; GPG para cifrar y firmar emails, archivos y commits de Git.

SSH — Autenticación con clave pública

BASH — SSH: generar claves y configurar autenticación sin contraseña
## GENERAR PAR DE CLAVES SSH

# Ed25519 — recomendado (ECC moderno, clave pequeña y segura)
ssh-keygen -t ed25519 -C "tu_email@ejemplo.com" -f ~/.ssh/id_ed25519
Generating public/private ed25519 key pair.
Enter passphrase: ****   ← proteger la clave privada con passphrase
Your identification has been saved in /home/user/.ssh/id_ed25519
Your public key has been saved in /home/user/.ssh/id_ed25519.pub

# RSA-4096 — alternativa compatible con sistemas más antiguos
ssh-keygen -t rsa -b 4096 -C "tu_email@ejemplo.com" -f ~/.ssh/id_rsa

## VER LAS CLAVES
cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... tu_email@ejemplo.com
← esta es la clave PÚBLICA — puedes copiarla a cualquier servidor

## COPIAR LA CLAVE PÚBLICA AL SERVIDOR
ssh-copy-id -i ~/.ssh/id_ed25519.pub usuario@servidor.com
# Esto añade tu clave pública al ~/.ssh/authorized_keys del servidor

## CONECTAR SIN CONTRASEÑA (usando la clave privada)
ssh -i ~/.ssh/id_ed25519 usuario@servidor.com

## QUÉ OCURRE DURANTE LA AUTENTICACIÓN SSH
1. El servidor genera un número aleatorio y lo cifra con tu clave pública
2. Te lo envía cifrado — solo tu clave privada puede descifrarlo
3. Tu cliente descifra el desafío y envía la respuesta
4. El servidor verifica que la respuesta es correcta → autenticado ✓
(nunca se envía la clave privada ni la contraseña por la red)

## FIRMAR COMMITS DE GIT CON SSH (GitHub, GitLab)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git commit -S -m "commit firmado con mi clave SSH"

GPG — Cifrar y firmar archivos y emails

BASH — GPG: generar clave, cifrar, descifrar y firmar
## GENERAR PAR DE CLAVES GPG
gpg --full-generate-key
# Seleccionar: RSA and RSA, 4096 bits, sin expiración (o con)
# Introducir nombre y email, proteger con passphrase

## VER LAS CLAVES GENERADAS
gpg --list-keys
pub   rsa4096 2024-01-15 [SC]
      A1B2C3D4E5F67890A1B2C3D4E5F67890A1B2C3D4
uid           [ultimate] Tu Nombre <tu@email.com>
sub   rsa4096 2024-01-15 [E]

## EXPORTAR LA CLAVE PÚBLICA (para compartir)
gpg --armor --export tu@email.com > mi_clave_publica.asc
cat mi_clave_publica.asc
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGb...
-----END PGP PUBLIC KEY BLOCK-----

## IMPORTAR LA CLAVE PÚBLICA DE OTRA PERSONA
gpg --import clave_publica_bob.asc

## CIFRAR un archivo para Bob (solo Bob puede descifrarlo)
echo "Mensaje secreto para Bob" > secreto.txt
gpg --encrypt --recipient bob@email.com --armor -o secreto.gpg secreto.txt

## DESCIFRAR (Bob lo ejecuta con su clave privada)
gpg --decrypt secreto.gpg
gpg: encrypted with RSA key, ID ...
Mensaje secreto para Bob

## FIRMAR un archivo (demostrar autoría)
gpg --sign --armor -o contrato.sig contrato.txt

## VERIFICAR la firma
gpg --verify contrato.sig contrato.txt
gpg: Good signature from "Tu Nombre <tu@email.com>" [ultimate]

## CIFRAR Y FIRMAR a la vez (lo más común)
gpg --encrypt --sign --recipient bob@email.com --armor -o mensaje.gpg mensaje.txt

## FIRMAR COMMITS DE GIT CON GPG
git config --global user.signingkey TU_ID_GPG
git config --global commit.gpgsign true
git commit -m "commit con firma GPG automática"
Mundo Real — 03
📦 JWT — Tokens Firmados Digitalmente

JSON Web Tokens (JWT) son el estándar para tokens de autenticación en APIs web modernas. La criptografía asimétrica permite que el servidor firme los tokens con su clave privada y cualquier servicio los verifique con la clave pública — sin necesitar una base de datos central de sesiones.

Estructura de un JWT

JWT — Estructura y componentes
# Un JWT tiene 3 partes separadas por puntos, cada una en Base64URL:
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNzAwMDAwMDAwfQ.FIRMA_AQUI
#  ↑ Header                ↑ Payload                                    ↑ Signature

# HEADER (decodificado):
{"alg": "RS256", "typ": "JWT"}
← RS256 = RSA + SHA-256 para la firma

# PAYLOAD (decodificado):
{"sub": "user123", "exp": 1700000000, "role": "admin"}
← claims: quién es el usuario y qué puede hacer

# SIGNATURE:
RSA_SIGN(SHA256(base64(header) + "." + base64(payload)), clave_privada_servidor)
← el servidor firma el header+payload — nadie puede falsificar el token sin la clave privada
Python — Generar y verificar JWT con RS256
# pip install pyjwt cryptography
import jwt
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

## Generar par de claves RSA para el servidor de autenticación
clave_privada = rsa.generate_private_key(public_exponent=65537, key_size=2048)
clave_publica = clave_privada.public_key()

## SERVIDOR — Emitir token al hacer login
payload = {
    "sub": "user123",
    "name": "Juan García",
    "role": "admin",
    "exp": 9999999999  # expiración
}

token = jwt.encode(payload, clave_privada, algorithm="RS256")
print(f"Token JWT: {token[:80]}...")

## CUALQUIER MICROSERVICIO — Verificar el token con la clave pública
try:
    datos = jwt.decode(token, clave_publica, algorithms=["RS256"])
    print(f"✓ Token válido. Usuario: {datos['name']}, Rol: {datos['role']}")
except jwt.InvalidTokenError as e:
    print(f"✗ Token inválido: {e}")

## Intentar falsificar el token (modificar el payload)
import base64, json
partes = token.split('.')
payload_falso = base64.urlsafe_b64encode(
    json.dumps({"sub": "hacker", "role": "superadmin"}).encode()
).rstrip(b'=').decode()
token_falso = f"{partes[0]}.{payload_falso}.{partes[2]}"

try:
    jwt.decode(token_falso, clave_publica, algorithms=["RS256"])
except jwt.InvalidTokenError:
    print("✗ Token falsificado rechazado — la firma no coincide")
Token JWT: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
✓ Token válido. Usuario: Juan García, Rol: admin
✗ Token falsificado rechazado — la firma no coincide
Mundo Real — 04
📜 Certificados X.509 y PKI

Un certificado digital X.509 es una clave pública con una identidad firmada por una autoridad de confianza (CA). Es la respuesta al problema de "¿cómo sé que esta clave pública pertenece realmente a google.com y no a un atacante?".

El problema que resuelven los certificados

Si Alice publica su clave pública, ¿cómo sabe Bob que realmente es la de Alice y no la de un atacante que se hace pasar por ella? Los certificados X.509 añaden una firma de una tercera parte de confianza (la CA) que avala la identidad.

📜 Cadena de confianza PKI
🏛 Root CA
ISRG Root X1
(confiada por el SO)
→ firma →
🔗 Intermediate CA
Let's Encrypt R3
→ firma →
📄 Certificado
banco.com
clave pública del banco
Tu navegador confía en la Root CA → confía en la Intermediate CA → confía en el certificado del banco

Crear un certificado auto-firmado con OpenSSL

BASH — Generar certificado X.509 auto-firmado
## En un solo comando: genera clave + CSR + certificado auto-firmado
openssl req -x509 -newkey rsa:4096 -keyout clave_servidor.pem \
  -out certificado.pem -days 365 -nodes \
  -subj "/CN=miservidor.local/O=GhostAcademy/C=ES"

## Ver el contenido del certificado
openssl x509 -in certificado.pem -text -noout | grep -E "Subject|Issuer|Validity|Public Key"
Subject: CN=miservidor.local, O=GhostAcademy, C=ES
Issuer: CN=miservidor.local, O=GhostAcademy, C=ES  ← auto-firmado (sujeto = emisor)
Validity:
    Not Before: Jan 15 14:32:00 2024 GMT
    Not After : Jan 15 14:32:00 2025 GMT
Public Key Algorithm: rsaEncryption (4096 bit)

## Proceso real de solicitud a una CA (CSR)
# 1. Generar clave privada
openssl genrsa -out mi_dominio.key 2048
# 2. Crear la CSR (Certificate Signing Request)
openssl req -new -key mi_dominio.key -out mi_dominio.csr \
  -subj "/CN=mi-dominio.com/O=Mi Empresa/C=ES"
# 3. Enviar mi_dominio.csr a la CA (Let's Encrypt, DigiCert...)
# 4. La CA verifica que controlas el dominio y firma el certificado
# 5. Recibes mi_dominio.crt firmado por la CA

## Let's Encrypt — certificado gratuito y automático
sudo certbot --nginx -d mi-dominio.com
# certbot gestiona todo: genera clave, envía CSR, obtiene cert, lo renueva
Referencia
📊 Comparativa de Algoritmos

Resumen de todos los algoritmos de criptografía asimétrica cubiertos en el manual, con sus características, usos recomendados y estado de seguridad actual.

AlgoritmoTipoLongitud claveUsos principalesEstado
RSA-2048Cifrado + Firma2048 bitsTLS legacy, JWT RS256, S/MIMEAceptable
RSA-4096Cifrado + Firma4096 bitsGPG, certificados de larga duraciónSeguro
ECDSA P-256Solo Firma256 bitsTLS, certificados web, JWT ES256Recomendado
ECDSA P-384Solo Firma384 bitsAlta seguridad, gobiernoRecomendado
Ed25519Solo Firma255 bitsSSH moderno, Signal, WireGuard★ Mejor opción
ECDH / ECDHEIntercambio de claves256-521 bitsTLS PFS, Signal ProtocolRecomendado
Diffie-HellmanIntercambio de claves2048+ bitsTLS legacy (sin ECC)Funcional
RSA-1024Cifrado + Firma1024 bits—ROTO — No usar
DSASolo Firma1024-3072 bits—Obsoleto — No usar

¿Cuándo usar cada algoritmo?

Necesito...UsarPor qué
Autenticación SSHEd25519Más rápido, clave más pequeña, resistente a ataques de canal lateral
Certificado TLS para webECDSA P-256Estándar moderno, soportado universalmente, perfecto forward secrecy
Firmar emails / archivos GPGRSA-4096 o Ed25519RSA-4096 para máxima compatibilidad; Ed25519 para uso moderno
JWT para autenticación webRS256 (RSA) o ES256 (ECDSA)RS256 más compatible; ES256 más eficiente
Cifrar datos para alguienRSA-OAEP o ECIESSiempre cifrado híbrido — RSA cifra la clave simétrica, AES cifra los datos
⚠ Criptografía post-cuánticaLos ordenadores cuánticos (cuando existan a escala suficiente) podrán romper RSA, ECDSA y DH usando el algoritmo de Shor. El NIST estandarizó en 2024 los primeros algoritmos post-cuánticos: ML-KEM (antes Kyber) para encapsulación de claves y ML-DSA (antes Dilithium) para firmas digitales. La migración a post-cuántica es el próximo gran reto de la industria.
Referencia
✅ Referencia Rápida

Comandos OpenSSL esenciales

BASH — Cheatsheet OpenSSL
## GENERAR CLAVES
openssl genrsa -out priv.pem 2048          # RSA-2048 privada
openssl rsa -in priv.pem -pubout -out pub.pem  # extraer pública
openssl genpkey -algorithm Ed25519 -out ed_priv.pem  # Ed25519 privada
openssl ecparam -name prime256v1 -genkey -out ec_priv.pem  # ECDSA P-256

## CIFRAR / DESCIFRAR (RSA)
openssl rsautl -encrypt -inkey pub.pem -pubin -in msg.txt -out msg.enc
openssl rsautl -decrypt -inkey priv.pem -in msg.enc

## FIRMAR / VERIFICAR
openssl dgst -sha256 -sign priv.pem -out firma.sig archivo.txt
openssl dgst -sha256 -verify pub.pem -signature firma.sig archivo.txt

## CERTIFICADOS
openssl req -x509 -newkey rsa:2048 -keyout k.pem -out cert.pem -days 365 -nodes
openssl x509 -in cert.pem -text -noout    # ver certificado
openssl verify -CAfile ca.pem cert.pem    # verificar cadena

## INSPECCIONAR SERVIDOR TLS
echo | openssl s_client -connect dominio.com:443 | openssl x509 -noout -text
openssl s_client -connect dominio.com:443 -tls1_3  # forzar TLS 1.3

## SSH
ssh-keygen -t ed25519 -C "email"          # generar par SSH
ssh-copy-id usuario@servidor              # copiar pública al servidor
ssh-keygen -lf ~/.ssh/id_ed25519.pub      # ver fingerprint

Conceptos clave para recordar

  • La clave pública se comparte libremente — con ella cualquiera puede cifrar o verificar firmas
  • La clave privada NUNCA se comparte — con ella descifras y firmas
  • Cifrar: usa la clave pública del destinatario. Firmar: usa tu propia clave privada
  • La criptografía asimétrica es lenta — en la práctica se usa solo para negociar claves simétricas (cifrado híbrido)
  • RSA se basa en la dificultad de factorizar números grandes; ECC en el logaritmo discreto en curvas elípticas
  • Una firma digital = hash del documento cifrado con la clave privada del firmante
  • Un certificado X.509 = clave pública + identidad + firma de la CA
  • Ed25519 es la mejor opción para firmas digitales hoy (SSH, Git, mensajería)
  • Perfect Forward Secrecy: cada sesión TLS usa claves efímeras — comprometer la clave del servidor no descifra sesiones pasadas
  • La criptografía post-cuántica (ML-KEM, ML-DSA) es el futuro — los algoritmos actuales son vulnerables a ordenadores cuánticos suficientemente grandes