A la lliçó anterior vam assegurar qui pot accedir a cada dada. Ara protegim la dada en si. A02 – Errors Criptogràfics (Cryptographic Failures) agrupa tot el que surt malament quan la informació sensible no es xifra, es xifra amb algorismes febles, o es gestiona malament la protecció de claus i secrets. Al Top Ten 2021 aquesta categoria va absorbir l'antiga "Exposició de Dades Sensibles" de 2017: el focus va passar de la conseqüència (la dada exposada) a la causa (l'error criptogràfic que la va permetre).

L'impacte és directe sobre la confidencialitat: contrasenyes, dades personals, números de targeta (PAN) o tokens que acaben en mans equivocades per viatjar en clar, desar-se sense xifrar o protegir-se amb algorismes trencats. A BazarNube trobarem dues joies típiques del codi heretat: contrasenyes desades amb un hash obsolet i números de targeta emmagatzemats "per comoditat". Les arreglarem.

Avís legal i ètic: els exemples fan servir dades fictícies. No processis ni emmagatzemis dades reals de targetes fora d'un entorn certificat, i prova només sobre sistemes propis o amb autorització explícita.

Contingut

  1. Identificar dades sensibles i el seu cicle de vida
  2. Protecció en trànsit: TLS ben configurat
  3. Protecció en repòs: xifratge i gestió de claus
  4. Hashing de contrasenyes: bcrypt/argon2 davant de MD5/SHA1
  5. Dades que directament no hauries d'emmagatzemar (PAN, CVV)
  6. Gestió de secrets i claus
  7. Errors comuns i consells
  8. Exercicis i solucions

  1. Identificar dades sensibles i el seu cicle de vida

Abans de xifrar cal saber què protegir. A BazarNube classifiquem:

Dada Sensibilitat En trànsit En repòs
Contrasenya Crítica TLS Hash lent amb sal (mai xifratge reversible)
Dades personals (nom, adreça) Alta TLS Xifrades a la BD
PAN (número de targeta) Crítica (PCI-DSS) TLS Preferible no emmagatzemar; si és obligatori, tokenitzar/xifrar
CVV Crítica TLS Prohibit emmagatzemar-lo, ni xifrat
Token de sessió Alta TLS + cookie segura No persistir en clar

La regla de partida: minimitza. La dada més segura és la que no deses.

  1. Protecció en trànsit: TLS

Tot el trànsit de BazarNube (front React, API Express, mòdul Java) ha d'anar sobre HTTPS/TLS. Errors freqüents:

  • Endpoints interns "només HTTP" perquè "estan a la xarxa privada" (un atacant que entra a la xarxa els escolta).
  • TLS mal configurat: versions obsoletes (SSLv3, TLS 1.0/1.1), xifres febles.
  • Manca d'HSTS, que força el navegador a fer servir sempre HTTPS.
// app.js — forcar HTTPS i activar HSTS a Express
const helmet = require('helmet');
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: true }));
// Redirigir HTTP -> HTTPS (darrere d'un proxy/terminador TLS)
app.use((req, res, next) => {
  if (req.headers['x-forwarded-proto'] === 'http') {
    return res.redirect(301, 'https://' + req.headers.host + req.url);
  }
  next();
});

HSTS amb maxAge d'un any i includeSubDomains evita atacs de degradació (SSL stripping). Veurem les capçaleres de seguretat amb més detall a A05 (03-06); aquí ens interessa la part criptogràfica: xifrar el canal sempre.

  1. Protecció en repòs: xifratge i gestió de claus

Les dades personals dels clients es xifren a PostgreSQL. Distingeix:

  • Xifratge simètric (AES-256-GCM) per a dades que necessites recuperar en clar (p. ex. una adreça d'enviament).
  • Hashing (irreversible) per al que només necessites comparar (contrasenyes). Mai xifris una contrasenya de manera reversible.
// crypto/fieldCrypto.js — xifratge autenticat de camps amb AES-256-GCM
const crypto = require('crypto');
const KEY = Buffer.from(process.env.FIELD_KEY, 'hex'); // 32 bytes, des del gestor de secrets

function encrypt(plaintext) {
  const iv = crypto.randomBytes(12);               // IV unic per operacio
  const cipher = crypto.createCipheriv('aes-256-gcm', KEY, iv);
  const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();                 // integritat (GCM)
  return Buffer.concat([iv, tag, enc]).toString('base64');
}

function decrypt(blob) {
  const raw = Buffer.from(blob, 'base64');
  const iv = raw.subarray(0, 12), tag = raw.subarray(12, 28), data = raw.subarray(28);
  const decipher = crypto.createDecipheriv('aes-256-gcm', KEY, iv);
  decipher.setAuthTag(tag);
  return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8');
}

Punts clau: AES-GCM aporta xifratge i autenticitat (detecta manipulació); l'IV és aleatori i únic per operació; la clau viu en un gestor de secrets, mai al codi.

  1. Hashing de contrasenyes: el cas de BazarNube

Aquí tenim el clàssic del codi legacy. El registre d'usuaris desava així les contrasenyes:

// auth.js — VULNERABLE: hash rapid, sense sal, trencat
const crypto = require('crypto');
function storePassword(user, password) {
  const hash = crypto.createHash('md5').update(password).digest('hex');
  return db.query('UPDATE users SET pwd = $1 WHERE id = $2', [hash, user.id]);
}

Per què això és un desastre

  • MD5/SHA1 estan trencats per a aquest ús: són ràpids i hi ha col·lisions conegudes.
  • Sense sal: dos usuaris amb la mateixa contrasenya tenen el mateix hash; una rainbow table els revienta a l'instant.
  • Ràpids = dolents per a contrasenyes: un atacant amb una GPU prova milers de milions de MD5 per segon. Si roba la taula users, recupera la majoria de contrasenyes en hores.

Explotació il·lustrativa

Amb la BD filtrada, l'atacant pren el hash 5f4dcc3b5aa765d61d8327deb882cf99, el busca en qualsevol base de dades pública de hashes MD5 i obté a l'instant la contrasenya password. Sense sal ni cost, és gairebé instantani.

Codi corregit amb bcrypt

// auth.js — SEGUR: bcrypt amb sal automatica i cost ajustable
const bcrypt = require('bcrypt');
const COST = 12; // factor de treball; pujar amb el temps

async function storePassword(user, password) {
  const hash = await bcrypt.hash(password, COST); // genera i inclou la sal
  return db.query('UPDATE users SET pwd = $1 WHERE id = $2', [hash, user.id]);
}

async function verifyPassword(password, storedHash) {
  return bcrypt.compare(password, storedHash); // comparacio en temps constant
}

bcrypt genera una sal aleatòria per contrasenya (inclosa al mateix hash) i és deliberadament lent (el factor de cost 12 obliga a moltes iteracions), cosa que fa inviable el cracking massiu. La comparació és en temps constant, evitant atacs de temporització.

Comparativa d'algorismes

Algorisme Apte per a contrasenyes? Motiu
MD5 / SHA1 No Trencats, ràpids, sense sal
SHA-256 "a seques" No Massa ràpid per a contrasenyes
bcrypt Lent, amb sal, molt provat
argon2id Sí (preferit avui) Resistent a GPU/ASIC, cost en memòria
PBKDF2 Acceptable Vàlid amb moltes iteracions (ús heretat/FIPS)

Per a un servei nou, argon2id és la recomanació actual de l'OWASP Password Storage Cheat Sheet; bcrypt continua essent una opció sòlida i àmpliament disponible.

Migració sense conèixer les contrasenyes

BazarNube no pot "desxifrar" els MD5 antics per tornar-los a fer hash. L'estratègia és tornar a fer hash al pròxim login vàlid: si l'usuari encerta amb el hash vell, es recalcula amb bcrypt i es marca migrat. Els que no tornen, es forcen a reiniciar la contrasenya després d'un termini.

async function login(email, password) {
  const u = await getUser(email);
  if (u.pwd_alg === 'md5') {
    if (md5(password) !== u.pwd) return fail();
    await storePassword(u, password);          // rehash amb bcrypt
    await setAlg(u, 'bcrypt');
    return ok(u);
  }
  return (await verifyPassword(password, u.pwd)) ? ok(u) : fail();
}

  1. Dades que no hauries d'emmagatzemar

El mòdul de facturació Java desava el número de targeta complet (PAN) "per mostrar l'historial". Això entra en l'àmbit de PCI-DSS i és un risc enorme.

  • El CVV mai es pot emmagatzemar, ni xifrat, sota cap circumstància.
  • El PAN només s'ha d'emmagatzemar si és imprescindible, xifrat o tokenitzat.
  • El més habitual és delegar el pagament en un proveïdor (PSP) que retorna un token; BazarNube desa només els últims 4 dígits per mostrar, i res més.
// PaymentRecord.java (modul legacy) — VULNERABLE
record PaymentRecord(String pan, String cvv, BigDecimal amount) {}  // desa PAN i CVV
// PaymentRecord.java — SEGUR: nomes un token del PSP i els ultims 4 digits
record PaymentRecord(String pspToken, String last4, BigDecimal amount) {}
// El PAN complet i el CVV mai toquen la nostra base de dades.

Menys dades sensibles emmagatzemades = menor superfície d'exposició i menor abast regulatori.

  1. Gestió de secrets i claus

De res serveix AES-256 si la clau està al repositori Git.

flowchart LR
  A[App BazarNube] -->|demana secret a l'arrencada| B[Gestor de secrets]
  B --> A
  C[Repositori Git] -. mai conte secrets .-> A

Bones pràctiques:

  • Secrets fora del codi: variables d'entorn injectades o un gestor de secrets (Vault, AWS Secrets Manager, etc.). Mai a Git, ni en un .env versionat.
  • Rotació de claus periòdica i davant sospita de compromís.
  • Separació de claus per entorn (dev/staging/prod) i per propòsit.
  • Si un secret es filtra en un commit, rota'l: purgar-lo de l'historial no n'hi ha prou, cal donar-lo per compromès.

Errors Comuns i Consells

  • Xifrar contrasenyes de manera reversible. Les contrasenyes es fan hash (bcrypt/argon2), no es xifren.
  • Fer servir SHA-256 sense més per a contrasenyes. És ràpid; necessites un algorisme lent amb sal.
  • Desar CVV o PAN complet sense necessitat. Delega en un PSP i desa només un token.
  • HTTP a la xarxa interna. El xifratge en trànsit s'aplica també dins del perímetre.
  • Claus al repositori. Fes servir un gestor de secrets; rota el que s'hagi filtrat.
  • Reutilitzar l'IV a AES. Cada operació necessita un IV únic; amb GCM, reutilitzar-lo trenca la seguretat.
  • Consell: classifica les dades primer. No pots protegir allò que no saps que tens.

Exercicis

Exercici 1. Un company proposa desar contrasenyes amb SHA-256 + sal. Resol el problema del MD5? Què recomanaries?

Exercici 2. Detecta els dos errors criptogràfics en aquest fragment i corregeix-los:

const key = 'clau-super-secreta-123';
const iv = Buffer.alloc(16, 0); // IV fix de zeros
const cipher = crypto.createCipheriv('aes-256-cbc', key.padEnd(32,'0'), iv);

Exercici 3. BazarNube necessita mostrar "targeta acabada en 4242" a l'historial. Què hauria d'emmagatzemar exactament i què no?

Solucions

Solució 1. Afegir sal a SHA-256 elimina les rainbow tables, però SHA-256 continua essent massa ràpid: un atac de força bruta amb GPU continua essent viable. La recomanació és un algorisme lent dissenyat per a contrasenyes: argon2id (preferit) o bcrypt, que incorporen sal i cost ajustable.

Solució 2. Dos errors: (1) l'IV és fix (tot zeros): trenca la confidencialitat en xifrar repetidament; ha de ser aleatori per operació (crypto.randomBytes). (2) La clau és una cadena al codi, farcida amb zeros: ha de ser una clau aleatòria de 32 bytes provinent del gestor de secrets. A més, convé AES-256-GCM (autenticat) en lloc de CBC. Vegeu la implementació de la secció 3.

Solució 3. Emmagatzemar únicament: un token del proveïdor de pagament (per a recobraments/referència) i els últims 4 dígits (last4) més potser la marca (Visa/Mastercard). Mai el PAN complet ni el CVV.

Conclusió

A02 ens recorda que protegir dades és un problema de causa (criptografia i gestió de claus), no només de conseqüència. Al backlog de BazarNube apuntem: TLS + HSTS en tot el trànsit, AES-256-GCM per a dades personals en repòs, migració de contrasenyes de MD5 a bcrypt/argon2 mitjançant rehash al login, eliminació del PAN/CVV a favor de tokens del PSP i trasllat de tots els secrets a un gestor amb rotació.

Entrada de backlog — A02: reemplaçat el hashing MD5 per bcrypt (cost 12) amb pla de migració; eliminat l'emmagatzematge de PAN/CVV al mòdul Java; xifratge de camps personals amb AES-GCM; secrets moguts fora de Git.

Hem protegit la dada en trànsit i en repòs. Però hi ha una família d'errors on la dada de l'usuari s'esmuny dins d'un intèrpret i en pren el control: la injecció. És la lliçó següent, A03:2021 – Injecció, amb SQLi a l'API Node/PostgreSQL de BazarNube i una injecció al mòdul legacy Java.

Curs d'OWASP: Directrius i Estàndards per a la Seguretat en Aplicacions Web

Mòdul 1: Introducció a OWASP

Mòdul 2: Principals Projectes d'OWASP

Mòdul 3: OWASP Top Ten 2021 en Profunditat

Mòdul 4: OWASP ASVS (Application Security Verification Standard)

Mòdul 5: OWASP SAMM (Software Assurance Maturity Model)

Mòdul 6: OWASP ZAP (Zed Attack Proxy)

Mòdul 7: Bones Pràctiques i Recomanacions

Mòdul 8: Exercicis Pràctics i Casos d'Estudi

Mòdul 9: Avaluació i Certificació

© Copyright 2026. Tots els drets reservats