Ir al contenido
HexaTransfer
Volver al blog
Cifrado y seguridad

Funciones de derivación de claves: PBKDF2, Argon2 y scrypt

Compara funciones de derivación de claves para cifrado basado en contraseña. Fortalezas y compensaciones de PBKDF2, Argon2 y scrypt.

Para claves de cifrado derivadas de contraseña en 2026, Argon2id es la opción recomendada (ganadora del PHC, favorita de OWASP, activamente defendida contra atacantes con GPU y ASIC); scrypt es una sólida segunda opción (resistente a memoria, ampliamente desplegada en criptomonedas); y PBKDF2-SHA-256 con 600.000+ iteraciones sigue siendo aceptable por compatibilidad pero ofrece resistencia mínima a GPU. Para aplicaciones de transferencia de archivos donde los usuarios introducen una contraseña para proteger un archivo compartido, Argon2id con 3 iteraciones, 64 MiB de memoria y paralelismo 4 es la línea base moderna. PBKDF2 sobrevive porque está integrado en la Web Crypto API y no requiere dependencia WASM. Aquí tienes cómo difieren las tres y cuándo tiene sentido cada una.

Tabla comparativa

| Propiedad | PBKDF2 | scrypt | Argon2id | |---|---|---|---| | Año de introducción | 2000 (RFC 2898) | 2009 (RFC 7914) | 2015 (ganador PHC) | | Resistente a memoria | No | Sí | Sí | | Flexibilidad de parámetros | Solo iteraciones | N, r, p | tiempo, memoria, paralelismo | | Resistencia a GPU | Débil | Moderada | Fuerte | | Resistencia a ASIC | Muy débil | Moderada | Fuerte | | Nativo en navegador (Web Crypto) | Sí | No | No | | Recomendación OWASP 2024 | Alternativa aceptable | Aceptable | Preferida | | Coste típico en navegador (hardware moderno) | 600.000 iter = ~500 ms | N=2^17 = ~800 ms | 3 iter, 64 MiB = ~1 s |

Por qué importa la resistencia a memoria

El modelo de amenaza para el cifrado basado en contraseña es la fuerza bruta offline. Un atacante obtiene el texto cifrado más la sal, recorre un diccionario de contraseñas e intenta derivar una clave que descifre con éxito. La defensa consiste en hacer cada intento costoso.

PBKDF2 hace cada intento costoso solo en tiempo de CPU (iteraciones SHA-256). Las GPU modernas ejecutan miles de millones de operaciones SHA-256 por segundo; una GPU de juegos puede probar 10-100 millones de adivinanzas PBKDF2-SHA-256-600000 al día. Los atacantes con ASIC son órdenes de magnitud mejores.

Las funciones resistentes a memoria (scrypt, Argon2) requieren una cantidad fija de memoria por intento. Las GPU y los ASIC tienen ancho de banda de memoria limitado, por lo que el paralelismo por dispositivo está acotado. Un requisito de memoria de 64 MiB significa que una GPU con 16 GB de VRAM puede ejecutar como máximo 256 adivinanzas en paralelo, no millones. El coste económico de la fuerza bruta sube 2-3 órdenes de magnitud.

PBKDF2: el estándar heredado

PBKDF2 (RFC 2898) itera una función pseudoaleatoria — típicamente HMAC-SHA-256 o HMAC-SHA-512 — sobre la contraseña y la sal. El recuento de iteraciones es el único parámetro ajustable.

const passwordKey = await crypto.subtle.importKey(
  "raw", new TextEncoder().encode(password),
  "PBKDF2", false, ["deriveKey"]
);
const aesKey = await crypto.subtle.deriveKey(
  {
    name: "PBKDF2",
    salt,  // 16 bytes aleatorios
    iterations: 600000,
    hash: "SHA-256",
  },
  passwordKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);

OWASP 2023 recomienda un mínimo de 600.000 iteraciones de PBKDF2-SHA-256. Algunas especificaciones (por ejemplo, el valor predeterminado de LastPass en 2018 de 100.100) se consideran demasiado bajas en 2026.

Ventajas: integrado en Web Crypto, sin WASM, validado por FIPS, compatible con la reanudación de sesión TLS 1.3, funciona en Node y navegadores de forma idéntica.

Limitaciones: sin resistencia a memoria, vulnerable a la aceleración por GPU y ASIC. Doblar las iteraciones dobla el coste del atacante, pero también el del usuario legítimo. En algún punto los usuarios legítimos se niegan a esperar y se limita el número de iteraciones.

scrypt: el primer despliegue resistente a memoria

scrypt (RFC 7914) fue inventado por Colin Percival en 2009 para Tarsnap. Mezcla el material de la contraseña a través de un gran buffer de memoria, obligando al atacante a mantener ese buffer durante cada intento.

Tres parámetros:

  • N: factor de coste (típicamente 2^14 a 2^20). El uso de memoria es aproximadamente 128 * N * r bytes.
  • r: tamaño de bloque (típicamente 8). Afecta a la memoria y al recuento de iteraciones GHASH.
  • p: paralelización (típicamente 1). Valores más altos aceleran el cálculo legítimo pero también al atacante; normalmente se deja en 1.

OWASP recomienda N=2^17, r=8, p=1 como línea base, que consume ~128 MiB y tarda unos 800 ms en hardware moderno.

scrypt no está en la Web Crypto API. En JavaScript, usa scrypt-js, @noble/hashes o libsodium.js. Litecoin y Dogecoin usan scrypt como prueba de trabajo, lo que ha incentivado el desarrollo de ASIC específicamente para scrypt, erosionando algo su ventaja asimétrica original frente a los ASIC.

Argon2id: el estándar de 2026

Argon2 ganó el Password Hashing Competition en 2015. Tres variantes: Argon2d (la más rápida, dependiente de datos, vulnerable a canales laterales), Argon2i (independiente de datos, más lenta), Argon2id (híbrida, recomendada para la mayoría de usos). El RFC 9106 la estandarizó en 2021.

Tres parámetros:

  • t (tiempo): iteraciones a través de la memoria. Típicamente 2-3.
  • m (memoria): memoria en KiB. Típicamente 65536 (64 MiB) o más.
  • p (paralelismo): grado de paralelismo. Típicamente 1-4.

Línea base OWASP 2024: t=2, m=19456 (19 MiB), p=1 como mínimo, y t=3, m=65536 (64 MiB), p=4 para protección más fuerte.

import { argon2id } from '@noble/hashes/argon2';
import { utf8ToBytes } from '@noble/hashes/utils';

const derivedKey = argon2id(utf8ToBytes(password), salt, {
  t: 3, m: 65536, p: 4, dkLen: 32
});

O mediante argon2-browser (WASM):

import argon2 from 'argon2-browser';
const hash = await argon2.hash({
  pass: password, salt,
  type: argon2.ArgonType.Argon2id,
  time: 3, mem: 65536, parallelism: 4, hashLen: 32
});

Argon2id derrota a los atacantes con GPU de manera más efectiva que scrypt porque su patrón de acceso a memoria es menos adecuado para diseños de memoria masiva. Los ASIC para Argon2 existen en investigación, pero no están desplegados económicamente a escala atacante todavía.

Elegir parámetros para tu aplicación

El método de calibración: elige la espera más larga que tus usuarios toleran (normalmente 500 ms a 2 segundos), mide en el dispositivo objetivo más lento y ajusta los parámetros para alcanzar ese presupuesto.

Para comparticiones de archivos protegidas con contraseña al estilo HexaTransfer, donde la derivación ocurre una vez en la subida y una vez en la descarga, 1-2 segundos es aceptable. Parámetros:

  • PBKDF2-SHA-256: 600.000-1.200.000 iteraciones
  • scrypt: N=2^17, r=8, p=1
  • Argon2id: t=3, m=65536, p=4

Para sistemas de login donde el usuario espera tras escribir su contraseña, 300-500 ms es el techo de UX. Los parámetros se reducen aproximadamente a la mitad. Para escenarios por lotes donde el usuario no espera (por ejemplo, re-cifrado en segundo plano), aumenta hasta 3-5 segundos.

Gestión de la sal

Las tres KDF necesitan una sal. Reglas:

  • Mínimo 16 bytes aleatorios
  • Generada mediante crypto.getRandomValues(), nunca Math.random()
  • Única por contraseña (si Alicia y Bob usan la misma contraseña, sus sales deben diferir para que las claves derivadas difieran)
  • No es secreta — almacénala junto al texto cifrado

A veces se discute el uso de pepper (un secreto añadido a todas las derivaciones). Para transferencia de archivos donde el "servidor" es un almacén de blobs sin inteligencia, el pepper no añade valor ya que no hay ningún secreto del lado del servidor. Para sistemas basados en cuentas, un pepper del lado del servidor almacenado separadamente de la base de datos de contraseñas hace que los volcados de base de datos sean menos útiles para los atacantes.

Migrar entre KDF

Si tienes un despliegue existente en PBKDF2 y quieres pasar a Argon2id:

  • Almacena el identificador de KDF en los metadatos del texto cifrado ("kdf": "pbkdf2-sha256-600000" o "kdf": "argon2id-3-65536-4")
  • En nuevas subidas, usa Argon2id
  • Al descifrar, lee el identificador de KDF y usa la función correspondiente
  • Nunca actualices ciegamente textos cifrados antiguos; necesitarías la contraseña para volver a derivar

LastPass, 1Password y Bitwarden han pasado todos por esta migración. Los tres ahora usan PBKDF2 con 600.000+ iteraciones por defecto, con Argon2id disponible en versiones más recientes.

¿Qué pasa con bcrypt?

bcrypt (1999) es una buena función de hash de contraseñas con modesta resistencia a memoria. Limita la entrada a 72 bytes (un famoso error — las contraseñas largas se truncan silenciosamente). Es el estándar histórico en Ruby on Rails y muchos frameworks PHP. Para código nuevo en 2026, prefiere Argon2id; bcrypt está bien para mantener sistemas existentes.

La recomendación realista

Para un nuevo servicio de transferencia de archivos en 2026:

  • Si puedes incluir una dependencia de 15-200 KB: Argon2id vía @noble/hashes o libsodium.js
  • Si necesitas cero dependencias y el tamaño del bundle es prioritario: PBKDF2-SHA-256 con 600.000 iteraciones vía Web Crypto
  • Si estás escribiendo una cartera de criptomonedas o algo que hereda scrypt heredado: scrypt con N=2^17

Para aplicaciones de producción que manejan archivos sensibles, Argon2id merece la dependencia WASM. Para comparticiones protegidas con contraseña simples donde los usuarios generan de todos modos una contraseña aleatoria, PBKDF2 es adecuado porque la entropía está en la contraseña, no en la KDF.

Pruébalo en hexatransfer.com — gratis, sin cuenta, hasta 10 GB.

Envía archivos grandes de forma segura con cifrado de extremo a extremo

Transfiere archivos de hasta 10 GB gratis con cifrado de extremo a extremo. Sin necesidad de cuenta. Tus archivos se cifran en tu navegador antes de subirlos: nadie más puede leerlos.

Enviar un archivo