Ha arribat el moment de saldar el deute més vell del curs. Des de 02-01, cada vegada que fèiem servir AES-GCM o HMAC aparcàvem la mateixa pregunta: i com va arribar la clau secreta a tots dos costats sense que ningú la interceptés? A 04-01 la criptografia asimètrica va donar una resposta parcial (xifrar cap a una clau pública), però hi havia una altra idea, anterior i més elegant, publicada per Diffie i Hellman el 1976: dues persones que mai no s'han vist poden acordar un secret compartit parlant per un canal públic on tothom escolta, i tot i així l'espia no s'assabenta del secret. Sona a màgia; és aritmètica. En aquesta lliçó veuràs la intuïció (una barreja de colors), un Diffie-Hellman de joguina amb enters petits, la seva versió moderna amb X25519, com convertir el secret en una clau AES real amb HKDF, i què són l'efimeritat i la forward secrecy. I descobriràs la fallada fatal que arrossega tot això — un atac amb nom propi — que només les signatures de 04-03 i els certificats del mòdul 5 podran tancar.
Contingut
- El problema, plantejat amb precisió
- La intuïció: barrejar colors
- Diffie-Hellman de joguina amb enters petits
- ECDH modern: X25519 a pyca/cryptography
- Del secret compartit a la clau: HKDF
- Demostració completa: MediNube ↔ Clínica Sol
- Claus efímeres i forward secrecy
- La fallada fatal: home al mig
- Com es tanca el forat (i on)
El problema, plantejat amb precisió
Precisem el repte, perquè la seva solució és contraintuïtiva. Dues parts — MediNube i la Clínica Sol — volen acordar una clau secreta comuna per després xifrar amb AES-256-GCM (mòdul 2). Les condicions:
- Mai no han compartit res abans: no hi ha cap secret previ del qual partir.
- L'únic canal és públic: una atacant, a qui anomenarem sempre pel seu paper, pot llegir tot el que es transmet.
- Al final, totes dues han de tenir el mateix secret, i l'atacant — que ha vist passar cada byte — no ha de poder calcular-lo.
Que això sigui possible sembla violar el sentit comú: si l'espia ho veu tot el que es diu, com no podrà repetir el càlcul? La resposta és a les funcions d'un sol sentit que ja coneixes: s'intercanvien dades amb les quals cada part pot acabar el càlcul fent servir el seu secret privat, però amb les quals l'espia, sense cap secret privat, es queda a les portes.
La intuïció: barrejar colors
L'analogia clàssica, i la millor. Imagina que barrejar pintures és fàcil, però separar una barreja en els seus colors originals és pràcticament impossible (aquesta és la "funció d'un sol sentit"):
- MediNube i la Clínica Sol acorden en públic un color base, diguem-ne groc. L'atacant el veu: tant se val.
- Cada part tria en secret un color propi i no l'hi diu a ningú. MediNube tria vermell; la Clínica, blau.
- Cada part barreja el seu secret amb el groc públic i envia la barreja pel canal. MediNube envia taronja (groc+vermell); la Clínica envia verd (groc+blau). L'atacant veu el taronja i el verd volar per la xarxa.
- Cada part afegeix el seu propi secret a la barreja que ha rebut. MediNube agafa el verd i hi afegeix el seu vermell; la Clínica agafa el taronja i hi afegeix el seu blau. Totes dues arriben al mateix color final (groc+vermell+blau): un marró compartit.
flowchart TD
Base["Color base public: GROC (tothom el veu)"]
Base --> MN1["MediNube: groc + VERMELL secret = TARONJA"]
Base --> CS1["Clinica Sol: groc + BLAU secret = VERD"]
MN1 -->|envia TARONJA pel canal public| CS2
CS1 -->|envia VERD pel canal public| MN2
MN2["MediNube: VERD rebut + el seu VERMELL"] --> Final["= MARRO (groc+vermell+blau)"]
CS2["Clinica Sol: TARONJA rebut + el seu BLAU"] --> Final
Espia["Atacant: veu groc, taronja i verd... pero separar pintures es inviable"] -.no pot.-> Final
L'atacant té el groc, el taronja i el verd. Per arribar al marró necessitaria el vermell o el blau, i aquests mai no van viatjar: per extreure'ls hauria de "separar" una barreja, que és justament l'inviable. Els secrets privats (vermell, blau) mai no surten dels seus propietaris; només viatgen les barreges. Canvia "barrejar colors" per una operació matemàtica d'un sol sentit i tens Diffie-Hellman.
Diffie-Hellman de joguina amb enters petits
Avís de joguina (regla d'or 1). Números ridículament petits, sense cap de les comprovacions que exigeix un DH real. Serveix per veure el mecanisme, res més. En producció, X25519 de
pyca/cryptography.
L'operació d'un sol sentit del DH clàssic és l'exponenciació modular: pow(g, x, p) és fàcil; recuperar x a partir del resultat (el logaritme discret, parent de l'el·líptic de 04-02) és inviable amb p gran. El "groc" són dos números públics: un primer p i una base g.
# --- DIFFIE-HELLMAN DE JOGUINA: nomes per aprendre ---
# Parametres PUBLICS, acordats a la vista de tothom (el "groc").
p = 23 # primer (en real: >= 2048 bits)
g = 5 # base
# --- Secrets PRIVATS: cada part tria el seu del CSPRNG (regla d'or 3) ---
a = 6 # secret de MediNube (el "vermell"; mai es transmet)
b = 15 # secret de la Clinica (el "blau"; mai es transmet)
# --- Cada part publica la seva "barreja" (clau publica DH) ---
A = pow(g, a, p) # MediNube envia A = 5^6 mod 23 = 8 (el "taronja")
B = pow(g, b, p) # Clinica envia B = 5^15 mod 23 = 19 (el "verd")
print(f"Viatgen pel canal public: A={A}, B={B}")
# --- Cada part combina el que ha rebut amb EL SEU secret ---
secret_medinube = pow(B, a, p) # (g^b)^a mod p
secret_clinica = pow(A, b, p) # (g^a)^b mod p
print(secret_medinube, secret_clinica) # 2 i 2: el mateix!
assert secret_medinube == secret_clinicaEl cor és una igualtat de les potències: (g^b)^a = g^(b·a) = g^(a·b) = (g^a)^b (mod p). Cada part arriba a g^(a·b) mod p per un camí diferent, fent servir el seu propi exponent secret. L'atacant veu p, g, A=8 i B=19, però per calcular g^(a·b) necessitaria a o b, i extreure'ls d'A o B és el logaritme discret — inviable amb paràmetres grans.
# El que l'atacant NO pot fer eficientment amb p gran:
# recuperar 'a' des de A. Amb p=23 si (forca bruta); amb p de 2048 bits, no.
for candidat_a in range(p):
if pow(g, candidat_a, p) == A:
print(f"(nomes amb joguina) a = {candidat_a}") # a = 6
breakAquest bucle que trenca el joguina en microsegons és exactament el que es torna impossible en créixer p. El DH clàssic continua viu (en la seva variant de corba el·líptica), però amb números enormes i comprovacions que aquí omitim. Per això passem a la versió moderna.
ECDH modern: X25519 a pyca/cryptography
ECDH (Elliptic Curve Diffie-Hellman) és el mateix protocol sobre l'aritmètica de corbes de 04-02: els secrets són escalars, les claus públiques són punts, i la "combinació" és multiplicació escalar. Amb X25519 (la corba que MediNube va triar a 04-02 per a intercanvi) l'API amaga tota l'aritmètica rere un sol mètode, exchange():
from cryptography.hazmat.primitives.asymmetric import x25519 # Cada part genera el seu parell EFIMER (privada = escalar aleatori; publica = punt). priv_medinube = x25519.X25519PrivateKey.generate() priv_clinica = x25519.X25519PrivateKey.generate() # S'intercanvien NOMES les publiques pel canal (serialitzades a bytes/PEM). pub_medinube = priv_medinube.public_key() pub_clinica = priv_clinica.public_key() # Cada part combina LA SEVA privada amb la publica ALIENA -> mateix secret. secret_mn = priv_medinube.exchange(pub_clinica) secret_cs = priv_clinica.exchange(pub_medinube) print(secret_mn == secret_cs) # True print(len(secret_mn)) # 32 bytes de secret compartit
Mapeja cada línia contra el joguina i contra els colors: generate() és triar el secret (vermell/blau) més publicar la barreja (taronja/verd); exchange(publica_aliena) és el pas final de combinar. Res del secret privat surt mai de l'objecte. I com que generar un parell X25519 és pràcticament gratis (04-02), no hi ha problema a crear-ne un de nou per cada intercanvi — la base de la forward secrecy de l'apartat 7.
Del secret compartit a la clau: HKDF
Temptació fatal: fer servir secret_mn (aquests 32 bytes) directament com a clau AES-256. No ho facis. El secret d'un intercanvi DH/ECDH no és una clau uniforme: és un element matemàtic de la corba amb possible estructura i biaix, no una cadena de bits uniformement aleatòria. Ficar-lo tal qual a l'AES viola el principi que les claus han de ser uniformes.
L'eina correcta ja la coneixes de 02-04: HKDF (HMAC-based Key Derivation Function). L'HKDF "condensa" aquest secret en una clau criptogràficament uniforme de la mida que vulguis, i de passada permet lligar la clau a un context amb el paràmetre info — igual que fèiem amb b"medinube:historials:v1":
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
def derivar_clau_canal(secret_compartit: bytes) -> bytes:
"""Converteix el secret ECDH cru en una clau AES-256 uniforme."""
return HKDF(
algorithm=hashes.SHA256(),
length=32, # 256 bits per a AES-256-GCM
salt=None, # opcional; a TLS s'usen nonces del handshake
info=b"medinube:canal-clinica:v1", # ata la clau a AQUEST proposit (02-04)
).derive(secret_compartit)
clau_canal = derivar_clau_canal(secret_mn)
print(len(clau_canal)) # 32 bytes, llestos per a AES-256-GCML'info amb etiqueta versionada (:v1) és el mateix patró del curs: una clau derivada per al "canal-clinica" mai no col·lideix amb una altra derivada per a, diguem-ne, b"medinube:backup:v1", encara que totes dues surtin del mateix secret. Criptoagilitat (regla d'or 8) integrada en el nom.
Demostració completa: MediNube ↔ Clínica Sol
Ajuntem les tres peces — ECDH + HKDF + AES-GCM — pel que portàvem prometent des de 02-01: enviar un fragment d'historial de la Clínica Sol a MediNube establint la clau sobre la marxa, sense secret previ. Reaprofitem el format v1 del mòdul 2 (versio(0x01)||nonce(12)||xifrat+tag) i la seva AAD per pacient:
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.asymmetric import x25519
# ---- FASE 1: intercanvi de claus (canal public) ----
# Cada part genera el seu parell efimer i s'envien les publiques serialitzades.
priv_cs = x25519.X25519PrivateKey.generate()
priv_mn = x25519.X25519PrivateKey.generate()
pub_cs_bytes = priv_cs.public_key().public_bytes_raw() # 32 bytes pel canal
pub_mn_bytes = priv_mn.public_key().public_bytes_raw() # 32 bytes pel canal
# ---- FASE 2: cada part deriva LA MATEIXA clau de canal ----
pub_mn = x25519.X25519PublicKey.from_public_bytes(pub_mn_bytes)
pub_cs = x25519.X25519PublicKey.from_public_bytes(pub_cs_bytes)
clau_cs = derivar_clau_canal(priv_cs.exchange(pub_mn)) # costat Clinica
clau_mn = derivar_clau_canal(priv_mn.exchange(pub_cs)) # costat MediNube
assert clau_cs == clau_mn # mateix secret
# ---- FASE 3: la Clinica xifra un historial amb AES-256-GCM (modul 2) ----
pacient = "ana.perez"
aad = f"paciente={pacient};formato=v1".encode() # AAD del curs
historial = b"Al.lergies: penicil.lina. Grup sanguini: A+. Ultima visita: 2026-07-01"
nonce = os.urandom(12) # nonce fresc (regla d'or 3)
sobre = bytes([0x01]) + nonce + AESGCM(clau_cs).encrypt(nonce, historial, aad)
# ---- FASE 4: MediNube desxifra ----
assert sobre[0] == 0x01 # versio de format
nonce_rebut, xifrat = sobre[1:13], sobre[13:]
desxifrat = AESGCM(clau_mn).decrypt(nonce_rebut, xifrat, aad)
print(desxifrat.decode())sequenceDiagram
participant CS as Clinica Sol
participant MN as MediNube
CS->>MN: publica efimera X25519 de CS (32 bytes, en clar)
MN->>CS: publica efimera X25519 de MN (32 bytes, en clar)
Note over CS,MN: cadascu: exchange() -> secret comu -> HKDF -> clau AES-256
CS->>MN: sobre v1 = 0x01 || nonce || AES-GCM(historial, AAD)
Note over MN: AES-GCM.decrypt -> historial de ana.perez
Ho hem aconseguit: dues parts sense secret previ, un canal on tot es veu, i tot i així un historial xifrat amb una clau que mai no va viatjar. Cada primitiva del curs ocupa el seu lloc: X25519 acorda, HKDF condensa, AES-GCM protegeix, l'AAD lliga el xifratge al pacient. Detall d'API: public_bytes_raw() / from_public_bytes() són la forma compacta (32 bytes crus) de X25519; també pots fer servir PEM (SubjectPublicKeyInfo) com a 04-01/04-02 si prefereixes un format autodescriptiu.
Claus efímeres i forward secrecy
Fixa't que a la demo cada parell X25519 es va generar amb generate() en el moment i no es va desar enlloc. Això és una clau efímera: s'usa per a un intercanvi i es descarta. L'alternativa serien claus estàtiques (fixes, reutilitzades a cada connexió). La diferència té un nom i conseqüències enormes: la forward secrecy (confidencialitat cap endavant, o perfect forward secrecy, PFS).
Imagina que l'atacant grava avui tot el trànsit xifrat entre la Clínica Sol i MediNube — no el pot llegir, però l'arxiva. I demà, per un descuit, un robatori o una ordre judicial, aconsegueix la clau privada d'una de les parts. Pot ara desxifrar el que va gravar?
| DH amb claus estàtiques | DH amb claus efímeres (amb PFS) | |
|---|---|---|
| Es genera un parell nou... | Una vegada, es reutilitza sempre | A cada sessió/connexió |
| Si roben la privada avui... | Desxifra tot el trànsit passat gravat | No desxifra res del passat |
| Cost | Menor (no es regeneren claus) | Mínim amb X25519 (generar és gairebé gratis, 04-02) |
| Recomanació | Evitar per a canals | Estàndard modern |
La clau de la forward secrecy: com que les privades efímeres es destrueixen després de cada intercanvi, comprometre la clau de llarg termini d'una part no revela els secrets de sessió ja negociats — aquests secrets ja no existeixen enlloc. Cada conversa queda protegida "cap endavant" davant de futurs compromisos. Aquí es cobra la promesa de 04-02: generar un parell EC és tan barat que fer-ho per connexió no costa res, i per això la forward secrecy és avui l'estàndard (la "E" final d'ECDHE a TLS significa justament ephemeral).
La fallada fatal: home al mig
I ara la mala notícia, la mateixa ombra que persegueix el mòdul sencer. Torna a la demostració i fes-te la pregunta de 04-01 i 04-03: quan MediNube rep pub_cs_bytes, com sap que aquests 32 bytes són de veritat la clau pública de la Clínica Sol? No ho sap. Són bytes que han arribat per un canal públic. I aquí es cola l'atac.
Apareix MalloryClinic, una atacant situada al mig del canal (un router compromès, una wifi hostil, un DNS enverinat). En lloc de limitar-se a escoltar (contra el qual el DH és immune), intercepta i substitueix les claus públiques: fa dos intercanvis, un amb cada part, fent-se passar per l'altra.
sequenceDiagram
participant CS as Clinica Sol
participant M as MalloryClinic (al mig)
participant MN as MediNube
CS->>M: publica de CS
M->>MN: publica de MALLORY (es fa passar per CS)
MN->>M: publica de MN
M->>CS: publica de MALLORY (es fa passar per MN)
Note over CS,M: CS acorda una clau... amb MALLORY
Note over M,MN: MN acorda una altra clau... amb MALLORY
CS->>M: sobre xifrat (clau CS-Mallory)
Note over M: desxifra, LLEGEIX l'historial, torna a xifrar
M->>MN: sobre xifrat (clau Mallory-MN)
El resultat és devastador i silenciós: la Clínica Sol estableix un canal xifrat amb Mallory creient parlar amb MediNube; MediNube n'estableix un altre amb Mallory creient parlar amb la Clínica. Mallory desxifra cada missatge amb una clau, el llegeix (l'historial de ana.perez, en clar, davant dels seus ulls), el torna a xifrar amb l'altra i el reenvia. Tots dos extrems veuen un canal que "funciona": els missatges arriben, es desxifren, tot sembla correcte. La matemàtica de Diffie-Hellman és perfecta; l'anella trencada és, una vegada més, l'autenticitat de les claus públiques.
Diffie-Hellman, per si sol, garanteix confidencialitat davant d'un espia passiu, però no davant d'un atacant actiu que pugui modificar el trànsit. El DH sense autenticar és una casa sense pany: preciosa per dins, oberta a qui empenyi la porta.
Com es tanca el forat (i on)
La cura és autenticar les claus públiques de l'intercanvi: que cada part pugui demostrar que la clau que envia és seva. I per "demostrar autoria" ja tens l'eina de la lliçó anterior: signatures digitals. Si la Clínica Sol signa la seva clau pública efímera amb la seva clau Ed25519 de llarg termini, MalloryClinic no la podrà substituir — no sap falsificar la signatura de la Clínica. Aquesta combinació (DH efímer per al secret + signatura de llarg termini per autenticar) és exactament la que fa servir el TLS modern, i té nom: DH efímer autenticat.
Però fixa't que això només trasllada el problema: per verificar la signatura de la Clínica, MediNube necessita la seva clau pública Ed25519 autèntica... i com sap aquesta que és de la Clínica? És la pregunta incòmoda de 04-01 i 04-03, ara ineludible: cal algú de confiança que certifiqui quina clau pública pertany a qui. Aquesta infraestructura — certificats X.509 i autoritats de certificació — és el mòdul 5, i TLS (que combina ECDHE + certificats + AES-GCM exactament com aquí, i que veuràs a 05-02) n'és l'aplicació estrella. Ho deixem, per última vegada, explícitament obert: ja tenim gairebé totes les peces; falta la que dona confiança a les claus públiques.
Abans d'anar-hi, queda una lliçó que uneix els dos fils d'aquest mòdul — xifrar cap a algú (04-01) i acordar una clau (aquesta) — en el patró que fa servir el món real per xifrar de veritat: el xifratge híbrid, a 04-05.
Errors Comuns i Consells
- Fer servir el secret ECDH cru com a clau. L'error número u.
exchange()no retorna una clau llesta: retorna material que ha de passar per HKDF. Saltar-se la KDF és una fallada de seguretat real, no un tecnicisme. - Creure que Diffie-Hellman autentica. No ho fa: dona confidencialitat davant d'un espia passiu i res davant d'un home al mig actiu. El DH acorda un secret amb qui sigui que estigui a l'altre costat, sigui qui sigui. Autenticar les claus és un pas a part (signatures + certificats).
- Reutilitzar claus DH estàtiques quan podries fer servir efímeres. Renuncies a la forward secrecy: un compromís futur de la clau desxifra tot el passat gravat. Amb X25519, generar un parell per sessió és gairebé gratis — fes-ho.
- Confondre X25519 amb Ed25519 (una altra vegada). X25519 és només intercanvi (
exchange); Ed25519 és només signatura (sign). No comparteixen API ni se substitueixen. Cada propòsit, el seu parell. - Reutilitzar el nonce d'AES-GCM entre missatges del canal. Que la clau vingui d'un DH no eximeix de la regla del mòdul 2: nonce fresc per missatge. Una clau de sessió no autoritza a repetir nonces.
- Implementar DH clàssic a mà amb
pow. El joguina omet validar els paràmetres, comprovar que les claus rebudes són al grup correcte i defensar-se de subgrups petits. X25519 va ser dissenyada perquè res d'això no t'explosioni: fes-la servir. - Oblidar l'
infod'HKDF o reutilitzar-lo entre propòsits diferents. L'etiquetab"medinube:canal-clinica:v1"és el que separa criptogràficament aquesta clau de qualsevol altra derivada del mateix secret. Una etiqueta clara i versionada per a cada ús.
Exercicis
-
Joguina a mà. Amb
p = 23,g = 5, secret de MediNubea = 4i secret de la Clínicab = 3: calculaA,Bi comprova que totes dues parts arriben al mateix secret. Després, posa't a la pell de l'atacant: recuperaaper força bruta des deA. Explica en una frase per què aquesta força bruta és l'atac que es torna impossible ambpde 2048 bits. -
Canal complet amb forward secrecy. Escriu
obrir_canal()que retorni la clau AES-256 del canal derivada per totes dues parts amb X25519 efímer + HKDF (info=b"medinube:canal-clinica:v1"), i fes servir aquesta clau perquè MediNube enviï a la Clínica el sobrev1d'un missatge curt amb AADpaciente=ana.perez;formato=v1. Després, genera un segon canal ambobrir_canal()i comprova que la clau és diferent de la primera. Quina propietat de seguretat acabes de demostrar i per què protegeix el trànsit ja gravat? -
L'atac en codi. Simula MalloryClinic sense xifratge, només amb les claus: genera parells efímers per a la Clínica, per a MediNube i dos per a Mallory. Calcula (a) el secret que creuria compartir la Clínica (la seva privada × la pública que li ha arribat, que és la de Mallory) i (b) el que creuria compartir MediNube. Comprova que Clínica i MediNube no comparteixen secret entre elles, però que Mallory en comparteix un amb cadascuna. Explica què hauria impedit l'atac.
Solucions
p, g, a, b = 23, 5, 4, 3 A = pow(g, a, p) # 5^4 mod 23 = 4 B = pow(g, b, p) # 5^3 mod 23 = 10 assert pow(B, a, p) == pow(A, b, p) # totes dues: 5^12 mod 23 = 18 # L'atacant recupera 'a' des de A per forca bruta: a_trencat = next(x for x in range(p) if pow(g, x, p) == A) # 4 print(a_trencat)
Aquesta força bruta —provar tots els exponents fins a reproduir A— és el logaritme discret. Amb p de 2048 bits el nombre d'exponents possibles és astronòmic i no existeix cap drecera eficient, així que l'atac que aquí triga microsegons passa a trigar més que l'edat de l'univers.
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.asymmetric import x25519
def obrir_canal():
priv_a, priv_b = x25519.X25519PrivateKey.generate(), x25519.X25519PrivateKey.generate()
clau_a = derivar_clau_canal(priv_a.exchange(priv_b.public_key()))
clau_b = derivar_clau_canal(priv_b.exchange(priv_a.public_key()))
assert clau_a == clau_b
return clau_a
clau1 = obrir_canal()
aad = b"paciente=ana.perez;formato=v1"
nonce = os.urandom(12)
sobre = bytes([0x01]) + nonce + AESGCM(clau1).encrypt(nonce, b"Cita confirmada 09:00", aad)
print(AESGCM(clau1).decrypt(sobre[1:13], sobre[13:], aad).decode())
clau2 = obrir_canal()
print(clau1 == clau2) # False: cada canal, clau diferentHas demostrat forward secrecy: com que cada sessió fa servir un parell efímer que es descarta, les claus de dues sessions no tenen relació. Si demà l'atacant roba una privada de llarg termini, no pot reconstruir la clau d'una sessió passada —aquesta privada efímera ja no existeix—, així que el trànsit gravat ahir continua il·legible.
from cryptography.hazmat.primitives.asymmetric import x25519 cs, mn = x25519.X25519PrivateKey.generate(), x25519.X25519PrivateKey.generate() m_cs, m_mn = x25519.X25519PrivateKey.generate(), x25519.X25519PrivateKey.generate() # A CS li arriba la publica de Mallory (m_cs) creient que es de MN; i viceversa. secret_cs = derivar_clau_canal(cs.exchange(m_cs.public_key())) secret_mn = derivar_clau_canal(mn.exchange(m_mn.public_key())) # Mallory reprodueix tots dos amb les seves dues privades: secret_mallory_amb_cs = derivar_clau_canal(m_cs.exchange(cs.public_key())) secret_mallory_amb_mn = derivar_clau_canal(m_mn.exchange(mn.public_key())) print(secret_cs == secret_mn) # False: CS i MN NO comparteixen res print(secret_cs == secret_mallory_amb_cs) # True: Mallory desxifra a CS print(secret_mn == secret_mallory_amb_mn) # True: Mallory desxifra a MN
Clínica i MediNube creuen tenir un canal comú, però cadascuna el té amb Mallory, que llegeix i reenvia pel mig. L'hauria impedit autenticar les claus públiques: si la Clínica signés la seva pública efímera amb la seva Ed25519 de llarg termini (04-03), Mallory no la podria substituir sense falsificar aquesta signatura. I per verificar aquesta signatura cal conèixer l'Ed25519 autèntica de la Clínica —el problema de confiança del mòdul 5.
Conclusió
Saldat el deute de 02-01. Diffie-Hellman permet que dues parts sense secret previ acordin una clau comuna per un canal públic: cadascuna tria un secret privat, publica una "barreja" (pow(g, a, p) en el clàssic; un punt en ECDH), i combina la barreja aliena amb el seu propi secret per arribar al mateix resultat —mentre l'espia, que ho ha vist tot, es queda fora pel logaritme discret. A la pràctica es fa servir X25519 amb exchange(), el seu secret cru mai no s'usa directe sinó que passa per HKDF (info=b"medinube:canal-clinica:v1") per obtenir la clau AES-256-GCM, i les claus es generen efímeres per guanyar forward secrecy: robar la clau de llarg termini no desxifra el passat. Ho vas muntar sencer —X25519 + HKDF + AES-GCM— per enviar un historial de la Clínica Sol a MediNube. Però el DH té un taló d'Aquil·les que no és matemàtic sinó d'identitat: sense autenticar les claus públiques, MalloryClinic s'asseu al mig i ho llegeix tot sense que ningú se n'adoni. L'autenticació comença amb les signatures de 04-03 i es completa amb els certificats del mòdul 5 —i TLS (05-02) fa exactament el que has fet aquí, ECDHE inclòs. Abans d'això, una última peça del mòdul: unir "xifrar cap a algú" i "acordar una clau" en el patró amb què el món real xifra de veritat. És el xifratge híbrid, a 04-05. Ens veiem allà.
Curs de Criptografia Aplicada
Mòdul 1: Fonaments de la Criptografia
- Què és la criptografia i per a què serveix
- Codificació, ofuscació i xifratge
- Aleatorietat i entropia
- El principi de Kerckhoffs i les regles d'or
Mòdul 2: Criptografia Simètrica
- Xifratge simètric: AES i ChaCha20
- Modes d'operació
- Xifratge autenticat (AEAD)
- Derivació de claus (KDF)
Mòdul 3: Hashes, MAC i Contrasenyes
- Funcions hash criptogràfiques
- Autenticació de missatges amb HMAC
- Emmagatzematge segur de contrasenyes
Mòdul 4: Criptografia Asimètrica
- Fonaments de clau pública i RSA
- Criptografia de corba el·líptica
- Signatures digitals
- Intercanvi de claus: Diffie-Hellman
- Xifratge híbrid
Mòdul 5: PKI, Certificats i TLS
- Certificats X.509 i autoritats de certificació
- TLS a la pràctica
- Gestió del cicle de vida dels certificats
