Base64 vs Base64url: los dos caracteres que cambian, el relleno y cómo convertir entre ambos
Base64 es una forma de escribir bytes usando solo 64 caracteres imprimibles, y Base64url es la misma codificación con dos caracteres cambiados y, normalmente, sin el relleno final. Esa pequeña diferencia basta para que un JWT no se pueda verificar, un enlace de descarga devuelva 404 o un decodificador lance "carácter no válido". Saber exactamente qué cambia y qué formato espera cada sistema convierte esos errores en un arreglo de una línea.
Qué es Base64, en una frase
Base64 toma los bytes de tres en tres (24 bits), los divide en cuatro grupos de 6 bits y representa cada grupo con un carácter del alfabeto A–Z, a–z, 0–9, + y /. El resultado ocupa un tercio más que el original y puede viajar por cualquier sistema de texto: JSON, correo, cabeceras HTTP o URLs, este último solo con la variante adecuada.
La diferencia en una tabla
RFC 4648 define las dos variantes. Comparten los primeros 62 caracteres y solo difieren en los dos últimos del alfabeto y en el relleno.
Base64 (sección 4) Base64url (sección 5)
valor 62 + -
valor 63 / _
relleno = obligatorio = normalmente omitido
seguro en URLs no (+ / = son especiales) sí
seguro en nombres no (/ separa carpetas) sí
bytes : FB FF BF
base64 : +/+/
base64url : -_-_
texto : subjects?_d
base64 : c3ViamVjdHM/X2Q=
base64url : c3ViamVjdHM_X2Q
Por qué existe Base64url
En una URL, un + en la query string se decodifica como espacio, / separa segmentos de ruta y = separa claves de valores. Un valor Base64 normal colocado en una URL sin codificar se corrompe en silencio: a+b llega como a b y el decodificador falla o, peor, devuelve otros bytes.
Codificarlo con porcentajes lo arreglaría (+ pasa a %2B, / a %2F), pero alarga el valor y es fácil codificarlo dos veces. Base64url evita el problema usando dos caracteres que nunca hay que escapar. Quitar el relleno ahorra caracteres y evita = en la query string; la longitud del string ya indica cuánto relleno habría.
Lo mismo vale para nombres de archivo y claves de objetos: / separa carpetas, así que Base64 normal no sirve como nombre de archivo ni como clave de S3.
Dónde se usa cada uno
Base64url sin relleno: las tres partes de un JWT (RFC 7515 lo exige), code_verifier y code_challenge de PKCE en OAuth, identificadores de credenciales en WebAuthn, los campos de las JSON Web Keys y muchos tokens de enlaces firmados.
Base64 normal con relleno: data URIs (data:image/png;base64,…), adjuntos de correo MIME, la autenticación HTTP Basic, certificados y claves PEM, valores de Secrets de Kubernetes y la mayoría de las API JSON que transportan binarios.
Cuando la documentación no lo aclara, mira un ejemplo real: cualquier - o _ indica Base64url, y cualquier + o / indica Base64 normal.
Cómo convertir entre ambos
La conversión es mecánica: cambiar los dos caracteres y quitar o restaurar el relleno. El relleno se restaura añadiendo = hasta que la longitud sea múltiplo de 4.
const aBase64url = (b64) =>
b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const aBase64 = (b64url) => {
const s = b64url.replace(/-/g, '+').replace(/_/g, '/');
return s + '='.repeat((4 - (s.length % 4)) % 4);
};
// Node.js entiende base64url directamente
Buffer.from('hello?', 'utf8').toString('base64url'); // 'aGVsbG8_'
import base64
def b64url_decode(s: str) -> bytes:
return base64.urlsafe_b64decode(s + '=' * (-len(s) % 4))
# Terminal: decodificar el payload de un JWT (Base64url sin relleno)
payload=$(printf '%s' "$JWT" | cut -d. -f2 | tr '_-' '/+')
while [ $(( ${#payload} % 4 )) -ne 0 ]; do payload="$payload="; done
printf '%s' "$payload" | base64 -d
Un resto de 1 es imposible: ningún Base64 válido tiene longitud 4n + 1. Si te encuentras ese caso, se perdieron caracteres, normalmente por un recorte o por una copia incompleta.
Errores al mezclar las dos variantes
Decodificar Base64url con un decodificador estricto de Base64 falla en el primer - o _. Los decodificadores permisivos, como base64_decode de PHP por defecto, ignoran esos caracteres y devuelven bytes incorrectos sin avisar, que es peor.
Poner Base64 normal en una query string sin codificarlo convierte cada + en un espacio. Si tienes que usar Base64 normal en una URL, codifícalo con encodeURIComponent o cambia a Base64url.
Comparar valores codificados como strings falla cuando uno conserva el relleno y el otro no: YQ== y YQ son el mismo byte. Decodifica ambos y compara los bytes.
Generar tokens aleatorios seguros para URLs
Base64url es la forma habitual de convertir bytes aleatorios en un token compacto para enlaces de recuperación, claves de API o códigos de invitación. 32 bytes aleatorios dan 256 bits de entropía en 43 caracteres.
// Node.js
require('node:crypto').randomBytes(32).toString('base64url');
# Python
import secrets
secrets.token_urlsafe(32)
No construyas tokens con Math.random() ni con marcas de tiempo, las codifiques como las codifiques: la seguridad viene de la aleatoriedad de los bytes, no de la codificación.
Fuentes y lecturas recomendadas
RFC 4648 define Base64 (sección 4) y el alfabeto seguro para URLs y nombres de archivo (sección 5), incluido cuándo puede omitirse el relleno. RFC 7515 exige Base64url sin relleno en JWS, y RFC 7636 (PKCE) lo usa en los code challenges. La documentación de Buffer en Node.js describe la codificación base64url.
Frequently asked questions
¿Qué es Base64?
Una codificación que representa cualquier dato binario con 64 caracteres ASCII imprimibles, para que pueda viajar por sistemas de solo texto como JSON o el correo. No es cifrado: cualquiera puede decodificarlo.
¿Qué diferencia hay entre Base64 y Base64url?
Base64url sustituye `+` por `-` y `/` por `_`, y normalmente omite el relleno `=`. Todo lo demás es idéntico, así que convertir es cambiar caracteres y añadir o quitar relleno.
¿Por qué los JWT usan Base64url?
Los tokens viajan en URLs, cabeceras y cookies, donde `+`, `/` y `=` tendrían que escaparse o se malinterpretarían. RFC 7515 exige Base64url sin relleno en las tres partes de un JWS.
¿Cómo decodifico Base64url?
Cambia `-` por `+` y `_` por `/`, añade `=` hasta que la longitud sea múltiplo de 4 y usa un decodificador Base64 normal. En Node.js puedes usar directamente `Buffer.from(s, 'base64url')`.
¿Base64url es seguro en URLs y nombres de archivo?
Sí. Su alfabeto son letras, dígitos, `-` y `_`, y ninguno necesita codificación ni actúa como separador. Sin relleno, tampoco contiene `=`.
¿Cómo sé qué variante usa un string?
Busca los caracteres distintivos: `-` o `_` indican Base64url, y `+` o `/` indican Base64 normal. El relleno `=` sugiere Base64 normal, pero su ausencia por sí sola no demuestra nada.