Aquesta lliçó salda el deute més antic del curs. Al mòdul 1 vas aprendre els quatre objectius de la criptografia i des de llavors n'has aconseguit tres: confidencialitat (mòdul 2), integritat i autenticitat (mòdul 3). El quart — el no repudi — va quedar promès, i a 03-02 vas veure exactament per què un MAC no ens el pot donar. Avui l'aconsegueixes amb la signatura digital: el propietari d'una clau privada signa, i qualsevol amb la pública verifica — sense secrets compartits, sense possibilitat de negar l'autoria. Veuràs la mecànica (es signa el hash, no el document), els dos esquemes que importen (RSA-PSS i Ed25519), i ho aplicaràs al cas central del mòdul: les receptes electròniques de MediNube, que la Dra. Lucía Ferrer de la Clínica Sol signarà i una farmàcia externa verificarà. Pel camí et toparàs amb un enemic inesperat — la canonicalització del JSON — i amb la pregunta incòmoda del mòdul, que aquí es torna urgent.
Contingut
- El quart objectiu: per què el MAC no n'hi ha prou
- La taula definitiva: hash vs HMAC vs signatura digital
- La mecànica: signar el hash, verificar amb la pública
- Signatures amb RSA: PSS, i per què no PKCS#1 v1.5
- Signatures amb Ed25519: l'API mínima
- El cas central: receptes electròniques signades
- L'enemic silenciós: la canonicalització
- Signar documents: empremta + signatura
- L'advertència legal: això no és (encara) una signatura qualificada
- La farmàcia i la pregunta de sempre
El quart objectiu: per què el MAC no n'hi ha prou
Recorda el webhook signat de 03-02: MediNube i la Clínica Sol comparteixen una clau HMAC, i la capçalera X-MediNube-Firma demostra que el missatge el va generar algú amb aquesta clau i que no s'ha alterat. Integritat i autenticitat: aconseguides. Però imagina't aquesta disputa:
La Clínica Sol afirma: "MediNube ens va enviar l'ordre d'esborrar l'historial de ana.perez, aquí està el missatge amb el seu HMAC vàlid". MediNube respon: "Aquest HMAC no demostra res: la clínica també té la clau i podia haver fabricat el missatge ella mateixa".
I MediNube té raó. Amb una clau compartida, tot el que un pot autenticar, l'altre ho pot falsificar. Un HMAC vàlid demostra que el va generar una de les dues parts, però no quina — i davant d'un tercer (un jutge, un auditor), això és no poder demostrar res. Això és l'absència de no repudi: l'emissor sempre pot repudiar (negar) l'autoria.
La signatura digital trenca l'empat amb l'asimetria de 04-01: es signa amb la clau privada, que només té una persona, i es verifica amb la pública, que pot tenir tothom. Si la signatura verifica, la va generar el propietari de la privada — ningú més ho va poder fer, ni tan sols qui verifica. L'emissor ja no pot negar l'autoria (mentre la seva privada no hagi estat robada — d'aquí l'obsessió per protegir-la).
La taula definitiva: hash vs HMAC vs signatura digital
A 03-02 vam deixar aquesta taula anunciada amb una columna a les fosques. Aquí està completa — és de les taules més importants del curs:
| Hash (SHA-256) | HMAC (HMAC-SHA256) | Signatura digital (Ed25519, RSA-PSS) | |
|---|---|---|---|
| Usa clau? | No | Sí, compartida | Sí, parell pública/privada |
| Integritat (detectar alteració) | Sí* | Sí | Sí |
| Autenticitat (saber qui ho ha emès) | No | Sí, entre qui comparteix la clau | Sí, davant de qualsevol |
| No repudi (l'emissor no pot negar) | No | No (totes dues parts poden generar el MAC) | Sí (només el propietari de la privada signa) |
| Qui pot verificar? | Qualsevol amb el hash de referència | Només qui té la clau secreta | Qualsevol amb la clau pública |
| Cost computacional | Mínim | Mínim | Més gran (encara que l'Ed25519 és molt ràpid) |
| Cas d'ús a MediNube | Empremta d'exports (integritat.py), tokens (recuperacio.py) |
Webhooks (webhooks.py) |
Receptes electròniques (aquesta lliçó) |
* El hash només dona integritat si el valor de referència arriba per un canal íntegre — la lliçó de 03-01.
Regla mental per triar: si emissor i verificador són el mateix sistema o dues parts que confien mútuament i només temen tercers, l'HMAC n'hi ha prou i és més senzill. Tan bon punt necessitis que tercers verifiquin o que l'emissor no pugui desdir-se'n, necessites signatura digital. Una recepta que verificarà qualsevol farmàcia d'Espanya és el cas de manual.
La mecànica: signar el hash, verificar amb la pública
Conceptualment, signar un document de 10 MB no aplica l'operació asimètrica als 10 MB (recorda de 04-01 que l'RSA només processa uns centenars de bytes, i seria lentíssim). L'esquema universal és:
- Signar: l'emissor calcula el hash del missatge (SHA-256 o similar) i aplica l'operació de signatura amb la seva clau privada sobre aquest hash. El resultat — la signatura — són unes desenes o centenars de bytes que viatgen junt amb el missatge.
- Verificar: el receptor recalcula el hash del missatge rebut i comprova, amb la clau pública de l'emissor, que la signatura es correspon amb aquest hash. Si el missatge ha canviat un sol bit, o la signatura la va fer una altra clau, la verificació falla.
sequenceDiagram
participant D as Dra. Ferrer (privada)
participant R as Xarxa / emmagatzematge
participant F as Farmacia (publica de la Dra.)
D->>D: h = SHA-256(recepta)
D->>D: signatura = sign(privada, h)
D->>R: recepta + signatura
R->>F: recepta + signatura
F->>F: h' = SHA-256(recepta rebuda)
F->>F: verify(publica, signatura, h')
alt signatura valida
F->>F: la recepta es integra i la va emetre la Dra. OK
else InvalidSignature
F->>F: alterada o d'una altra clau: REBUTJAR
end
Fixa't en les propietats que emergeixen:
- Tot el que vas aprendre de hashes a 03-01 s'hereta: la signatura cobreix el document sencer, i la resistència a col·lisions del hash és part de la seguretat de la signatura (per això signar amb MD5/SHA-1 està trencat de veritat, no en teoria).
- La signatura no xifra: el missatge viatja en clar junt amb ella. Signatura i xifratge són objectius independents i es combinen quan calen tots dos (ho faràs a 04-05).
- A les APIs de
pyca/cryptographyel hash intern el fa la llibreria: tu passes el missatge complet asign()/verify(). La mecànica de dos passos queda a dins, però entendre-la et farà falta a l'apartat 8.
Signatures amb RSA: PSS, i per què no PKCS#1 v1.5
Si la clau és RSA (com la de l'hospital públic que exigeix RSA a la integració de MediNube), la signatura necessita un esquema de farciment — la història rima amb la de 04-01. N'hi ha dos:
- PKCS#1 v1.5 (signatura): l'heretat, determinista, omnipresent en sistemes antics. A diferència del seu cosí de xifratge, no està trencat — però la seva seguretat té demostracions més febles i les implementacions descurades de la seva verificació han produït atacs històrics (falsificacions de Bleichenbacher del 2006 contra verificadors laxos).
- RSA-PSS (Probabilistic Signature Scheme): el modern. Incorpora sal aleatòria (cada signatura del mateix document és diferent) i té proves de seguretat sòlides. És a la signatura RSA el que l'OAEP és al xifratge RSA: la forma correcta en codi nou.
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
priv_rsa = rsa.generate_private_key(public_exponent=65537, key_size=3072)
pub_rsa = priv_rsa.public_key()
missatge = b"Informe mensual d'activitat - MediNube"
pss = padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.DIGEST_LENGTH, # sal = mida del hash: el valor recomanat
)
signatura = priv_rsa.sign(missatge, pss, hashes.SHA256())
print(len(signatura)) # 384 bytes: sempre la mida del modul, com a l'OAEP
# Verificacio: no retorna True/False, LLANCA EXCEPCIO si falla.
from cryptography.exceptions import InvalidSignature
try:
pub_rsa.verify(signatura, missatge, pss, hashes.SHA256())
print("Signatura valida")
except InvalidSignature:
print("SIGNATURA INVALIDA: document alterat o clau incorrecta")Dos detalls d'API que t'has de gravar:
verifyno retorna un booleà: si la signatura és vàlida no retorna res, i si no ho és llançaInvalidSignature. És un disseny deliberat — impossible ignorar el resultat per accident. El teu codi ha de capturar l'excepció i tractar la fallada com el que és: un rebuig.- La signatura RSA-PSS és aleatòria: signa dues vegades el mateix missatge i obtindràs dues signatures diferents, totes dues vàlides. Mai no comparis signatures entre elles per "verificar" — verifica sempre amb
verify.
Signatures amb Ed25519: l'API mínima
Per al sistema de receptes, MediNube va decidir a 04-02 fer servir Ed25519. La seva API és la més petita possible — sense farciment a configurar, sense hash a triar (fa servir internament SHA-512), sense sal a ajustar:
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.exceptions import InvalidSignature
priv = ed25519.Ed25519PrivateKey.generate()
pub = priv.public_key()
missatge = b"Recepta de prova"
signatura = priv.sign(missatge) # i ja esta
print(len(signatura)) # 64 bytes, SEMPRE
try:
pub.verify(signatura, missatge) # i ja esta
print("Signatura valida")
except InvalidSignature:
print("SIGNATURA INVALIDA")Compara: 64 bytes de signatura davant de 384 de l'RSA-3072, i zero decisions de configuració. A més, l'Ed25519 és determinista: el mateix missatge amb la mateixa clau produeix sempre la mateixa signatura. Això no és un defecte (a diferència de l'RSA de llibre de text, aquí hi ha un disseny que ho fa segur) sinó una protecció: els esquemes de corba el·líptica clàssics (ECDSA, el que faries servir amb P-256) exigeixen un número aleatori fresc per signatura, i si aquest número es repeteix o és predictible, la clau privada es pot recuperar de les pròpies signatures — així es va extreure la clau de signatura de la PlayStation 3 el 2010. L'Ed25519 elimina aquesta decisió perillosa per construcció: la filosofia Bernstein de 04-02, aplicada. Si algun dia un tercer t'exigeix ECDSA amb P-256, pyca/cryptography ho fa bé (priv.sign(missatge, ec.ECDSA(hashes.SHA256()))); però quan triïs tu, Ed25519.
El cas central: receptes electròniques signades
Ara, el sistema de veritat. Requisits de l'equip mèdic de MediNube:
- La Dra. Lucía Ferrer (Clínica Sol, col·legiada fictícia
CS-4471) emet receptes per als seus pacients des deportal.medinube.example. - Una farmàcia externa — anomenem-la Farmàcia Robles — ha de poder verificar que la recepta la va emetre la doctora i que ningú no l'ha alterada (ni la dosi, ni el medicament, ni el pacient).
- Si hi ha una disputa ("jo mai vaig receptar això"), la signatura ha de poder-se presentar davant d'un tercer: no repudi.
Una recepta és un document estructurat. La representem com a JSON:
recepta = {
"formato": "receta-v1",
"medico": "dra.lucia.ferrer",
"colegiada": "CS-4471",
"paciente": "ana.perez",
"medicamento": "Amoxicil.lina 500 mg",
"posologia": "1 comprimit cada 8 hores, 7 dies",
"fecha_emision": "2026-07-08",
}I aquí apareix un problema que no és criptogràfic sinó de representació: la signatura es calcula sobre bytes, i un mateix diccionari es pot convertir en bytes de moltes maneres diferents.
L'enemic silenciós: la canonicalització
El primer intent del codi heretat de MediNube era aquest:
import json
def signar_recepta_MALAMENT(recepta: dict, priv) -> bytes:
# MALAMENT: json.dumps sense fixar ordre ni separadors.
return priv.sign(json.dumps(recepta).encode("utf-8"))Què falla? Que json.dumps no produeix sempre els mateixos bytes per al mateix contingut:
- L'ordre de les claus depèn de l'ordre d'inserció del diccionari. La farmàcia, en reconstruir la recepta des de la seva base de dades, pot obtenir les claus en un altre ordre → JSON diferent → hash diferent →
InvalidSignaturesobre una recepta perfectament legítima. - Els separadors per defecte (
", "i": ", amb espais) poden diferir entre llibreries i llenguatges (i si la farmàcia verifica en Java o en JavaScript?). - Els caràcters no ASCII es poden serialitzar escapats (
"Pérez") o literals ("Pérez"), segons l'opcióensure_ascii.
El resultat en producció: verificacions que fallen aleatòriament, i la temptació fatal d'"arreglar-ho" relaxant la verificació. La solució correcta és la canonicalització: definir una única serialització vàlida i fer-la servir sempre, en signar i en verificar. Amb això ja podem escriure el mòdul real, medinube/receptes.py:
# medinube/receptes.py
import json
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.exceptions import InvalidSignature
def canonicalitzar_recepta(recepta: dict) -> bytes:
"""Serialitzacio canonica: mateixes dades -> mateixos bytes, SEMPRE.
- sort_keys=True: ordre alfabetic de claus, no d'insercio.
- separators sense espais: sense ambiguitat de format.
- ensure_ascii=False + UTF-8: una unica representacio d'accents.
"""
return json.dumps(
recepta, sort_keys=True, separators=(",", ":"), ensure_ascii=False,
).encode("utf-8")
def signar_recepta(recepta: dict, priv_metge: ed25519.Ed25519PrivateKey) -> str:
"""Signa la recepta i retorna la signatura en hex, estil 'v1=<hex>' (com a 03-02)."""
signatura = priv_metge.sign(canonicalitzar_recepta(recepta))
return "v1=" + signatura.hex()
def verificar_recepta(recepta: dict, signatura: str,
pub_metge: ed25519.Ed25519PublicKey) -> bool:
"""True si la signatura correspon a la recepta i a la clau publica donades."""
if not signatura.startswith("v1="):
return False # versio desconeguda: rebutjar
try:
pub_metge.verify(bytes.fromhex(signatura[3:]), canonicalitzar_recepta(recepta))
return True
except (InvalidSignature, ValueError):
return False # ValueError: hex malformatI el flux complet, de la consulta a la farmàcia:
# A la consulta: la Dra. Ferrer signa (la seva privada es va carregar del PEM # xifrat de 04-02, amb la seva passphrase - mai viu al codi). priv_dra = ed25519.Ed25519PrivateKey.generate() # en real: load_pem_private_key(...) pub_dra = priv_dra.public_key() signatura = signar_recepta(recepta, priv_dra) print(signatura[:20], "...") # v1=8f3a... (64 bytes en hex) # A la Farmacia Robles: verificacio amb la PUBLICA de la doctora. print(verificar_recepta(recepta, signatura, pub_dra)) # True # Un atacant (o un error) canvia la dosi: recepta_alterada = dict(recepta, medicamento="Amoxicil.lina 1000 mg") print(verificar_recepta(recepta_alterada, signatura, pub_dra)) # False # La farmacia reconstrueix la recepta amb les claus en UN ALTRE ordre: recepta_reordenada = dict(reversed(list(recepta.items()))) print(verificar_recepta(recepta_reordenada, signatura, pub_dra)) # True <- canonicalitzacio
L'última línia és la victòria sobre signar_recepta_MALAMENT: l'ordre de les claus ja no importa, perquè tots dos costats canonicalitzen abans de tocar la criptografia. Aquest problema és tan universal que hi ha estàndards dedicats (JCS — JSON Canonicalization Scheme, RFC 8785); la nostra recepta de sort_keys + separators + UTF-8 és la versió mínima del mateix principi, suficient si controles tots dos extrems.
Signar documents: empremta + signatura
I quan el que cal signar no és un JSON de 300 bytes sinó l'export RGPD de 200 MB de 03-01? Passar-lo sencer a sign() obliga a tenir-lo a la memòria i que el verificador l'hagi de processar complet. El patró pràctic reaprofita el que ja tens a medinube/integritat.py: la funció empremta_exportacio que calcula el SHA-256 del fitxer per blocs. Se signa l'empremta:
# medinube/integritat.py ja ens dona l'empremta (03-01):
# empremta = empremta_exportacio("export_ana_perez.zip") # hex del SHA-256
def firmar_export(ruta: str, priv) -> str:
"""Signa l'empremta SHA-256 de l'export: document = empremta + signatura."""
empremta = empremta_exportacio(ruta) # hash per blocs, de 03-01
return "v1=" + priv.sign(empremta.encode("ascii")).hex()
def verificar_export_signat(ruta: str, signatura: str, pub) -> bool:
empremta = empremta_exportacio(ruta) # recalcular SEMPRE
try:
pub.verify(bytes.fromhex(signatura[3:]), empremta.encode("ascii"))
return True
except (InvalidSignature, ValueError):
return FalseConceptualment no hi ha res de nou — la signatura sempre signava un hash per dins; aquí simplement fem explícit el primer pas per poder fer el hash per blocs. El que hi guanya MediNube: l'export RGPD que abans només tenia empremta (integritat) ara té empremta signada (integritat + autenticitat + no repudi): Ana Pérez pot demostrar davant d'un tercer que aquest export li'l va emetre MediNube i no el va fabricar ella.
L'advertència legal: això no és (encara) una signatura qualificada
Parada obligatòria abans que ningú desplegui això en una farmàcia real. El que hem construït és una signatura digital criptogràfica, tècnicament sòlida. Però "signatura electrònica" amb plens efectes legals és un concepte jurídic regulat a la UE pel reglament eIDAS, que distingeix nivells (simple, avançada, qualificada) amb requisits que van molt més enllà de les matemàtiques: certificats qualificats emesos per prestadors acreditats, dispositius segurs de creació de signatura, segellat de temps... I una recepta mèdica real afegeix la seva pròpia normativa sanitària i de protecció de dades (RGPD, dades de categoria especial).
El nostre sistema de receptes és un exemple tècnic amb dades fictícies. Un desplegament real exigiria revisió de seguretat i de compliance per especialistes (eIDAS, RGPD, normativa de recepta electrònica) — com tots els sistemes d'aquest curs que toquen dades sanitàries o signatures amb valor legal. La criptografia que estàs aprenent és la base comuna de tots aquests nivells legals; l'embolcall jurídic no és matèria d'aquest curs.
La farmàcia i la pregunta de sempre
Queda l'elefant a l'habitació, i ja l'has vist venir. Tot el flux de la Farmàcia Robles comença amb "la farmàcia té pub_dra, la clau pública de la Dra. Ferrer". I com l'ha obtinguda? Com sap que és d'ella?
Si un atacant aconsegueix que la farmàcia accepti la seva clau pública com "la de la Dra. Ferrer", pot signar receptes falses que verificaran perfectament. La matemàtica de la signatura és impecable; l'anella trencada és, un altre cop, la identitat de la clau pública — la mateixa esquerda de 04-01, ara amb receptes de medicaments en joc. La solució real (que algú de confiança certifiqui que aquesta clau pertany a aquesta doctora, amb número de col·legiada inclòs) és exactament el mòdul 5. Fins llavors, la deixem explícitament oberta — i a la propera lliçó la veuràs convertida en un atac actiu amb nom propi.
Errors Comuns i Consells
- Tractar
verifycom si retornés un booleà. No retorna res o llançaInvalidSignature. L'error típic és cridarverifysensetry/except"per provar" i deixar que l'excepció tombi el procés, o al revés: capturarExceptiona seques i empassar-se errors que no són de signatura. CapturaInvalidSignatureespecíficament i tracta-la com un rebuig. - Comparar signatures entre elles. "Signo el que he rebut i comparo amb la signatura adjunta" funciona de casualitat amb esquemes deterministes i falla sempre amb RSA-PSS (aleatori). L'operació de verificació existeix per alguna cosa: fes-la servir.
- Signar JSON sense canonicalitzar. El bug de
signar_recepta_MALAMENT: verificacions que fallen segons l'ordre de claus, l'idioma o la llibreria de l'altre extrem. Canonicalitza als dos costats, sempre. - PKCS#1 v1.5 per signar en codi nou. No està trencat com el seu cosí de xifratge, però el PSS és superior en tot. Reserva el v1.5 per interoperar amb sistemes que l'exigeixin, documentant-ho.
- MD5 o SHA-1 com a hash de signatura. Aquí les col·lisions maten directament: s'han fabricat parells de documents diferents amb la mateixa signatura SHA-1 vàlida. SHA-256 com a mínim (03-01).
- Creure que la signatura xifra. La recepta viatja en clar junt amb la seva signatura; qualsevol pot llegir-la. Si a més cal confidencialitat (una recepta és dada sanitària), cal xifrar a més de signar — el combo arriba a 04-05.
- Confondre "xifrar amb la privada" amb signar. És una descripció folklòrica de com funcionava l'RSA per dins, i és perjudicial: no descriu el PSS, no descriu l'Ed25519, i porta a APIs mal utilitzades. Signar és signar: una operació pròpia, amb el seu farciment i la seva semàntica.
- Descuidar la privada del signant. El no repudi val el que valgui la custòdia de la clau: si la privada de la doctora és en un PEM sense xifrar en un repositori, qualsevol "és" la doctora. PEM xifrat (04-01/04-02) avui; gestor de secrets i HSM al mòdul 6.
Exercicis
-
Detector d'alteracions. Genera un parell Ed25519 i signa el missatge
b"Derivacio de ana.perez al Centre Medic Luna". (a) Verifica la signatura correcta. (b) Altera un sol byte del missatge i comprova que saltaInvalidSignature. (c) Altera un byte de la signatura i comprova que també. (d) Verifica el missatge original amb la clau pública d'un altre parell acabat de generar: què passa i què significa? -
Caçant el bug de canonicalització. Fent servir
signar_recepta_MALAMENT(signajson.dumps(recepta).encode()sense opcions): construeixrecepta_bamb els mateixos parells clau-valor quereceptaperò inserits en ordre invers, signareceptai intenta verificar la signatura contrajson.dumps(recepta_b).encode(). Comprova que falla. Repeteix ambcanonicalitzar_receptai comprova que totes dues serialitzacions produeixen bytes idèntics (==). Explica per què aquest bug és especialment traïdor en producció. -
La disputa. La Farmàcia Robles presenta una recepta de 900 mg d'un medicament amb signatura
v1=...que verifica amb la clau pública de la Dra. Ferrer. La doctora afirma que ella va receptar 500 mg. Raona (sense codi): (a) pot la doctora sostenir que la farmàcia va alterar la dosi després de la signatura? (b) Quines dues explicacions queden dempeus? (c) Què hauria passat en el mateix escenari si el sistema fes servir HMAC amb clau compartida entre MediNube i la farmàcia?
Solucions
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.exceptions import InvalidSignature
priv = ed25519.Ed25519PrivateKey.generate()
pub = priv.public_key()
msg = b"Derivacio de ana.perez al Centre Medic Luna"
signatura = priv.sign(msg)
pub.verify(signatura, msg) # (a) no llanca res: valida
alterat = bytes([msg[0] ^ 1]) + msg[1:] # (b) un bit del primer byte
try:
pub.verify(signatura, alterat)
except InvalidSignature:
print("(b) alterar el missatge invalida la signatura")
signatura_trencada = bytes([signatura[0] ^ 1]) + signatura[1:]
try:
pub.verify(signatura_trencada, msg) # (c)
except InvalidSignature:
print("(c) alterar la signatura tambe la invalida")
altra_pub = ed25519.Ed25519PrivateKey.generate().public_key()
try:
altra_pub.verify(signatura, msg) # (d)
except InvalidSignature:
print("(d) clau diferent: invalida")(d) Llança InvalidSignature: una signatura només verifica amb la pública del parell que va signar. És la propietat que dona autenticitat — i també la que explota l'atac de l'apartat 10: si et colen una clau pública falsa "de la doctora", les signatures de l'atacant verificaran amb ella. Verificar uneix la signatura a una clau, no a una persona; unir clau i persona és el mòdul 5.
import json
recepta = {"medico": "dra.lucia.ferrer", "paciente": "ana.perez",
"medicamento": "Amoxicil.lina 500 mg"}
recepta_b = dict(reversed(list(recepta.items()))) # mateix contingut, un altre ordre
print(json.dumps(recepta) == json.dumps(recepta_b)) # False
print(canonicalitzar_recepta(recepta) == canonicalitzar_recepta(recepta_b)) # True
signatura = signar_recepta_MALAMENT(recepta, priv) # signa bytes en ordre A
try:
pub.verify(signatura, json.dumps(recepta_b).encode()) # verifica bytes en ordre B
except InvalidSignature:
print("Recepta legitima REBUTJADA per l'ordre de les claus")És traïdor perquè no falla en les proves: mentre signatura i verificació passin pel mateix diccionari en el mateix procés, l'ordre coincideix i tot verifica. Falla setmanes després, al sistema de la farmàcia, amb un altre llenguatge o una altra base de dades que retorna les claus en un altre ordre — una fallada intermitent que sembla un bug de la criptografia i acaba temptant algú a "relaxar" la verificació. La canonicalització elimina la classe sencera de fallades.
- (a) No: si la farmàcia hagués canviat la dosi després de signar, la verificació fallaria — la signatura cobreix cada byte de la recepta canonicalitzada. Que verifiqui demostra que aquests 900 mg són exactament el que es va signar amb aquesta clau. (b) En queden dues: o la doctora realment va signar 900 mg (i ara ho nega — justament el que el no repudi permet descartar que prosperi), o la seva clau privada està compromesa (algú més va signar amb ella). Per això la custòdia de la privada és inseparable del valor probatori, i per això existeixen la revocació i els HSM (mòduls 5 i 6). (c) Amb HMAC compartit no hi hauria res a discutir: la farmàcia pot generar MACs vàlids de qualsevol recepta, així que el MAC no demostra qui la va crear. És la diferència exacta entre autenticitat i no repudi — la primera fila que li faltava a la taula de 03-02, ja tancada.
Conclusió
El quart objectiu està aconseguit. La signatura digital inverteix el flux del xifratge asimètric — es signa amb la privada, qualsevol verifica amb la pública — i amb això dona el que cap MAC podia: no repudi, perquè només el propietari de la privada va poder generar la signatura. Saps com funciona per dins (es signa el hash del missatge, heretant tot el que has après de hashes), quins esquemes fer servir (RSA-PSS quan t'imposin RSA, Ed25519 quan triïs tu: 64 bytes, API mínima, determinista i sense decisions perilloses), i que verify parla amb excepcions (InvalidSignature), no amb booleans. A MediNube ha quedat funcionant medinube/receptes.py — canonicalització JSON (sort_keys, separadors fixos, UTF-8), signatura v1=<hex> — amb el qual la Dra. Ferrer signa receptes que la Farmàcia Robles verifica, i els exports RGPD han guanyat signatura sobre la seva empremta. Recorda les dues ombres: això és un exemple tècnic, no una signatura qualificada eIDAS; i tot l'edifici descansa que la clau pública de la doctora sigui de la doctora — pregunta que continua oberta fins al mòdul 5. Amb signatures i corbes a la motxilla, toca per fi saldar el deute original del curs, el que arrosseguem des de 02-01: com acorden una clau secreta dues parts que mai no s'han vist, parlant per un canal on tothom escolta. És l'intercanvi de claus de Diffie-Hellman, i t'espera a 04-04. 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
