← Blog ·

X25519 explicado para desarrolladores: intercambio de claves sin magia

Si alguna vez has visto "X25519" en el package.json de una librería de criptografía y has seguido adelante sin preguntarte qué hace exactamente, este post es para ti. No hace falta un doctorado en matemáticas para entenderlo — solo entender el problema que resuelve y por qué se eligió esta curva en concreto.

El problema: dos partes, un secreto compartido, sin canal seguro

Imagina que Alice y Bob quieren compartir una clave secreta, pero solo pueden comunicarse por un canal que un atacante puede espiar (internet, básicamente). Esto es el problema clásico que resuelve Diffie-Hellman (DH): permite que dos partes generen un secreto compartido sin haberlo transmitido nunca directamente.

El DH clásico usa aritmética modular sobre números grandes. Funciona, pero es lento y las claves son enormes. La versión con curvas elípticas (ECDH) consigue la misma propiedad matemática — conmutatividad del intercambio — con claves mucho más pequeñas y operaciones mucho más rápidas.

Curve25519: la curva, no el algoritmo

Curve25519 es una curva elíptica concreta, diseñada por Daniel J. Bernstein, pensada desde el principio para ser rápida y difícil de implementar mal. Sus propiedades clave:

  • Aritmética en tiempo constante más sencilla de conseguir (menos superficie para ataques de canal lateral).
  • Resistente a ataques de "curva inválida", un problema histórico de otras curvas donde un atacante podía enviar puntos fuera de la curva esperada.
  • Claves de 32 bytes, rápidas de generar y de operar incluso en hardware modesto.

X25519: la función ECDH sobre Curve25519

Aquí está el matiz que mucha gente se salta: Curve25519 es la curva; X25519 es la función de intercambio de claves (ECDH) definida sobre esa curva, especificada en el RFC 7748. Es lo que realmente llamas desde tu código.

El esquema, simplificado:

  1. Cada parte genera un par de claves: una privada (un escalar aleatorio de 32 bytes) y una pública (el resultado de multiplicar ese escalar por un punto base fijo de la curva).
  2. Alice envía su clave pública a Bob, y viceversa — por un canal que puede ser público.
  3. Alice calcula secreto = privadaAlice * públicaBob. Bob calcula secreto = privadaBob * públicaAlice. Por las propiedades de la curva, ambos llegan al mismo valor.

Un observador que solo ve las dos claves públicas no puede reconstruir ese secreto sin resolver el problema del logaritmo discreto sobre curvas elípticas, que es computacionalmente inviable con los tamaños de clave actuales.

En código, usando los primitivos abiertos de @koove/crypto:

import { generateIdentityKeyPair } from '@koove/crypto';

const alice = generateIdentityKeyPair();
const bob = generateIdentityKeyPair();

// alice.publicKey y bob.publicKey pueden compartirse abiertamente
// alice.privateKey y bob.privateKey nunca salen del dispositivo

Lo que X25519 NO te da: cifrado

Esto es importante y a menudo se ignora: X25519 solo produce un secreto compartido, no cifra nada por sí mismo. Ese secreto crudo tampoco debería usarse directamente como clave simétrica — hay que pasarlo por una función de derivación de claves (en el caso de Koove, HKDF-SHA256) para obtener una clave con las propiedades adecuadas, y luego usar esa clave con un cifrado autenticado como AES-256-GCM.

Este patrón — X25519 para acordar/envolver una clave, AES-256-GCM para cifrar los datos reales — se llama cifrado de sobre (envelope encryption), y es exactamente lo que usa Koove para proteger secretos:

import { generateIdentityKeyPair, encryptSecret, sealKey, openKey } from '@koove/crypto';

const bob = generateIdentityKeyPair();

// La clave de datos (AES-256-GCM) cifra el secreto una sola vez
const { envelope, dataKey } = encryptSecret('sk_live_...');

// Esa clave de datos se "sella" (wrap) con la clave pública X25519 de Bob
const sealedForBob = sealKey(dataKey, bob.publicKey);

// Solo el dispositivo de Bob, con su clave privada, puede abrirla
const recoveredKey = openKey(sealedForBob, bob.privateKey);

El servidor de Koove nunca ve dataKey en claro ni las claves privadas — solo ciphertext y sobres sellados. Esa es la diferencia entre "cifrado en tránsito" y cifrado de extremo a extremo de verdad.

Lo que X25519 tampoco te da: forward secrecy automático

Si las claves X25519 que usas son estáticas (una identidad fija por dispositivo o usuario, que es el modelo habitual en un gestor de secretos), no tienes forward secrecy por defecto: si esa clave privada se compromete en el futuro, un atacante podría descifrar sobres antiguos que sellaste con esa clave pública.

Protocolos como Signal resuelven esto con un ratchet (rotación continua de claves efímeras). Koove no implementa un ratchet — usa X25519 + AES-256-GCM con identidades por dispositivo, que es el diseño correcto para "recuperar un secreto cuando lo necesito", pero no es equivalente en garantías a un protocolo de mensajería como Signal. Merece la pena decirlo con claridad: son propiedades distintas para casos de uso distintos.

Por qué esto importa en la era del código generado por IA

Cuando un asistente de IA escribe una integración con Stripe o con una API de terceros, tiende a pegar la clave directamente en el código o en un .env sin cifrar. Entender X25519 no es un ejercicio académico: es la base de por qué un secreto puede vivir cifrado hasta el último momento, descifrándose solo en el dispositivo o backend autorizado, verificado con App Attest / Play Integrity y biometría.

En la práctica, para tu flujo de trabajo diario esto se traduce en:

koove set STRIPE_SECRET_KEY sk_live_xxx --env prod

Tu código solo referencia STRIPE_SECRET_KEY por nombre. El valor real nunca pasa por el asistente de IA ni por el repositorio.

Conclusiones prácticas

  • No implementes ECDH desde cero. Usa librerías auditadas (libsodium, noble-curves, o primitivos ya empaquetados como @koove/crypto, que son de código abierto y auditables).
  • X25519 resuelve el acuerdo de claves, no el cifrado completo — necesitas KDF + AEAD alrededor.
  • Si tu amenaza principal es "secretos filtrados en código generado por IA", el diseño correcto es cifrado de extremo a extremo con identidades verificadas por dispositivo, no forward secrecy tipo mensajería.

Puedes ver la arquitectura completa, incluidas las decisiones de diseño y sus límites, en el centro de confianza y seguridad, o revisar la documentación técnica del SDK y la CLI en /docs/sdk. Si tienes dudas concretas sobre el modelo de amenazas, el FAQ cubre las preguntas más comunes.

Prueba Koove

Si quieres dejar de pegar claves en texto plano en tu código y empezar a usar cifrado de extremo a extremo real desde el primer commit, crea tu cuenta gratuita y prueba koove set en tu proyecto en menos de cinco minutos.

Código generado por IA

Tu IA escribe el código. ¿Quién guarda los secretos?

El fallo de seguridad más común en apps generadas con IA son las credenciales expuestas. Con Koove, tu propio asistente guarda cada token desde la CLI — cifrado en tu máquina, nunca en el código ni en el repo.