Si administras un servidor o usas Git sobre SSH, tu clave es literalmente la llave de tu casa digital. Aun así, la mayoría de desarrolladores generó su par una vez con ssh-keygen y nunca entendió qué hay dentro. Este artículo cierra esa brecha: qué algoritmo elegir, cómo se serializa una clave en el formato OpenSSH y cómo generar pares válidos sin salir del navegador.
Ed25519 vs RSA: la elección ya está hecha
Dos familias dominan el mundo SSH:
RSA (1977) basa su seguridad en la dificultad de factorizar números enormes. Con 2048 bits sigue siendo aceptable; con 4096, más robusto pero notablemente más lento en cada handshake. Sus claves públicas son largas y la generación es computacionalmente cara.
Ed25519 (2011) usa criptografía de curva elíptica (Edwards curve con firma EdDSA sobre Curve25519). Con solo 256 bits ofrece seguridad equivalente a RSA-3072, con firmas más rápidas, claves públicas cortas (68 caracteres codificados) y determinismo que evita toda una clase de errores de implementación (no necesita nonce aleatorio por firma, fuente histórica de fugas de clave privada en ECDSA).
OpenSSH soporta Ed25519 desde la versión 6.5 (2014). Cualquier servidor razonablemente mantenido lo acepta hoy. La recomendación moderna es simple: Ed25519 por defecto, RSA-4096 solo cuando un sistema legacy exija RSA.
Anatomía de la clave pública
Una línea de authorized_keys tiene tres campos:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... miguel@portatil
El tipo (ssh-ed25519), el blob Base64 y un comentario libre. El blob oculta una estructura binaria definida por RFC 4253/4251 — no es un volcado de la clave cruda:
string "ssh-ed25519" ← nombre del algoritmo
string clave pública ← 32 bytes crudos
Cada string va precedido de su longitud como entero de 32 bits big-endian. Para RSA el orden importa y es específico: primero ssh-rsa, luego el exponente e y después el módulo n (en ese orden invertido respecto a la intuición). Los enteros usan el formato mpint, que antepone un byte 0x00 si el byte más significativo tiene el bit alto activo — la forma del protocolo de decir "esto es un positivo".
Puedes verificarlo tú mismo: decodifica el blob Base64 de cualquier clave pública con un conversor y verás los bytes ASCII de ssh-ed25519 justo al principio.
La clave privada: el contenedor openssh-key-v1
El formato antiguo PEM (-----BEGIN RSA PRIVATE KEY-----) se abandonó porque permitía ataques de fuerza bruta acelerados por hardware. Desde OpenSSH 6.5, el formato nativo es un contenedor binario propio cuyo encabezado Base64 empieza por el magic string openssh-key-v1\0. Su estructura:
string "openssh-key-v1"
string ciphername ← "none" si no hay passphrase
string kdfname ← "none"
string kdfoptions ← vacío
uint32 número de claves ← 1
string blob público
string sección privada:
uint32 checkint ← aleatorio...
uint32 checkint ← ...repetido dos veces
string tipo de clave
string campos privados
string comentario
padding ← bytes 1,2,3... hasta múltiplo de 8
El checkint duplicado es un mecanismo elegante de integridad: al generar, el cliente escoge un uint32 aleatorio y lo escribe dos veces; al leer, comprueba que ambas copias coinciden. Si un descifrado con passphrase errónea produce bytes corruptos, el check falla antes de intentar parsear basura.
El padding determinista (bytes incrementales 1, 2, 3...) garantiza que el bloque siempre termine alineado a 8 bytes y permite detectar corrupción también al final. Cuando cifras con passphrase, los campos ciphername y kdfname pasan de none a aes256-ctr y bcrypt, y toda la sección privada se cifra.
Generar pares dentro del navegador con WebCrypto
Lo interesante: ningún navegador expone "serializar a formato OpenSSH", pero sí las primitivas para construirlo. El flujo para Ed25519:
const pair = await crypto.subtle.generateKey(
{ name: "Ed25519" },
true,
["sign", "verify"]
);
const rawPub = await crypto.subtle.exportKey("raw", pair.publicKey);
const pkcs8 = await crypto.subtle.exportKey("pkcs8", pair.privateKey);
De la privada PKCS#8 sale un detalle práctico: la seed Ed25519 son simplemente los últimos 32 bytes del DER — la estructura ASN.1 la contiene como OCTET STRING final, así que un slice(-32) la extrae sin parsear nada. La clave privada Ed25519 es la seed: con esos 32 bytes y la pública (que se deriva de ella) se reconstruye todo el contenedor.
Para RSA el camino pasa por JWK:
const jwkPriv = await crypto.subtle.exportKey("jwk", pair.privateKey);
// { n, e, d, p, q, qi, ... } en base64url
Con esos componentes BigInt se reensamblan los mpints del blob público (e antes que n) y de la sección privada (n, e, d, iqmp, p, q — el orden canónico de OpenSSH).
La ventaja de hacerlo en el navegador: la clave nunca existe fuera de tu máquina. No viaja por red, no toca un disco compartido, no queda en el historial de shell. La aleatoriedad proviene del CSPRNG del navegador (crypto.getRandomValues), el mismo que usa para TLS.
Fingerprint y verificación
El fingerprint moderno de OpenSSH es SHA-256 del blob público completo, codificado en Base64 sin padding y prefijado con SHA256:. Es lo que muestra ssh-keygen -lf y lo que ves la primera vez que conectas a un servidor ("key fingerprint is SHA256:..."). Comparar ese hash por un canal confiable es la defensa contra man-in-the-middle en la primera conexión.
Higiene mínima de claves
- Una clave por dispositivo y contexto. Rotar al vender o retirar una máquina debe ser trivial.
- Permisos
600en la clave privada (chmod 600 ~/.ssh/id_ed25519). OpenSSH rechaza claves legibles por otros usuarios. - Passphrase siempre que la superficie de amenaza incluya robo del portátil. Sin ella, quien copie el fichero es tú.
- En
authorized_keys, restringe el alcance cuando puedas:from="10.0.0.*",no-port-forwarding ssh-ed25519 AAAA...limita origen y capacidades de cada clave.
Genera tu par ahora mismo
Nuestro generador de claves SSH hace todo este proceso en tu navegador: Ed25519, RSA-2048 o RSA-4096, construye tanto la línea de authorized_keys como el contenedor openssh-key-v1 completo byte a byte, y te muestra el fingerprint SHA-256. Copia, pega en ~/.ssh/ y añade la pública a tu servidor.
Preguntas frecuentes
¿Puedo usar la misma clave para GitHub y mi VPS? Técnicamente sí; por higiene, mejor separadas. Si comprometen una, no pierdes ambos accesos a la vez.
¿Mi navegador soporta Ed25519? Los navegadores basados en Chromium y Firefox modernos lo soportan en SubtleCrypto; Safari llegó más tarde y puede fallar según versión. Si tu navegador no lo implementa, el fallback correcto es RSA.
¿Las claves generadas en el navegador son tan seguras como las de ssh-keygen? Sí: la generación usa el mismo CSPRNG del sistema operativo y los mismos algoritmos. La diferencia real es que ssh-keygen puede proteger la privada con passphrase cifrada; si necesitas eso en local, genera aquí y protégela después con ssh-keygen -p -f id_ed25519.
Genera tu par Ed25519 o RSA con el generador de claves SSH, 100% en tu navegador y sin que la clave salga de tu equipo.