Comparison
Base64 vs base64url: dos caracteres que importan
Mismo alfabeto, últimos dos caracteres distintos, relleno diferente. Una línea de diferencia, casos de uso totalmente distintos.
By Buğra SözeriPublished
Resumen. Base64 y base64url son la misma codificación salvo por tres detalles definidos en el RFC 4648: + se convierte en -, / se convierte en _, y el relleno = se elimina. Usa base64url cuando el valor codificado viaje en una URL, JWT o cookie firmada; usa Base64 estándar para adjuntos de correo, HTTP Basic Auth y URIs de datos.
Ambos están definidos en el RFC 4648. El Base64 estándar (§4) y el base64url (§5) difieren en exactamente tres detalles: dos sustituciones de alfabeto y el tratamiento del relleno. Eso es todo. Pero mezclarlos rompe JWT, cookies firmadas y todo lo que viaja en una URL.
La diferencia
| Propiedad | Base64 estándar | Base64url |
|---|---|---|
| Carácter 62 | + | - |
| Carácter 63 | / | _ |
| Relleno | = hasta múltiplo de 4 | Normalmente omitido |
| ¿Seguro para URL? | No (+, /, = necesitan codificación porcentual) | Sí |
| Definido en | RFC 4648 §4 | RFC 4648 §5 |
La misma entrada, ambas codificaciones
Codificando https://example.com/?q=hello world:
- Estándar:
aHR0cHM6Ly9leGFtcGxlLmNvbS8/cT1oZWxsbyB3b3JsZA== - Base64url:
aHR0cHM6Ly9leGFtcGxlLmNvbS8_cT1oZWxsbyB3b3JsZA
Nótese que / en la forma estándar se convierte en_ en base64url, y el == final se elimina.
Por qué las URL necesitan su propia variante
El alfabeto Base64 estándar incluye + y /. Ambos están reservados en la sintaxis URL:
/es el separador de ruta.+se interpreta, por convención en cadenas de consulta codificadas en formulario, como un espacio.=es el delimitador clave-valor en las cadenas de consulta.
Insertar una cadena Base64 estándar en una URL requiere codificar porcentualmente los tres: + se convierte en %2B, / en %2F, = en %3D. La cadena se vuelve más larga y difícil de leer. base64url evita la codificación porcentual eligiendo caracteres que ya son seguros para URL.
Por qué el relleno es opcional en base64url
El Base64 estándar siempre rellena la salida hasta un múltiplo de 4 caracteres usando =. Esto no es matemáticamente necesario — el decodificador puede inferir la longitud original a partir de la longitud de salida módulo 4. El relleno existe para decodificadores en flujo que necesitan conocer el límite entre fragmentos concatenados.
Base64url omite el relleno porque: (a) es redundante, (b) = necesita codificación porcentual en URLs, y (c) los signos de igual al final de las URL a veces confunden a analizadores de rutas, CDN y claves de caché.
Dónde se usa cada uno
| Caso de uso | Variante |
|---|---|
| Adjuntos de correo (MIME) | Base64 estándar |
HTTP Basic Auth (Authorization: Basic …) | Base64 estándar |
URIs de datos (data:image/png;base64,…) | Base64 estándar |
| JWT (los tres segmentos) | Base64url, sin relleno |
| Verificador/desafío PKCE de OAuth 2.0 | Base64url, sin relleno |
| Claves WebPush, JOSE/JWS/JWE | Base64url, sin relleno |
| Cookies firmadas (Rails, Django, etc.) | Base64url, sin relleno |
El problema con la decodificación
El decodificador Base64 predeterminado de muchas bibliotecas falla con entrada base64url. El base64.b64decode de Python rechaza - y _; el Buffer.from(s, "base64") de Node.js es permisivo pero el inverso (.toString("base64")) produce Base64 estándar.
Solución entre lenguajes: reemplazar antes de decodificar.
// base64url → Base64 estándar
const standard = base64url
.replace(/-/g, "+")
.replace(/_/g, "/")
.padEnd(Math.ceil(base64url.length / 4) * 4, "=");Mejor: usa una biblioteca que soporte base64url explícitamente (Node 16+ tiene Buffer.from(s, "base64url"); Python tiene base64.urlsafe_b64decode).
La regla práctica
Si el valor codificado aparecerá en una URL, en un JWT, en una cookie firmada o como segmento de ruta — usa base64url. Si es para un adjunto de correo, cabecera HTTP (excepto cookies) o URI de datos en JSON — usa Base64 estándar.
Nuestro codificador Base64 gestiona ambas variantes con un selector de alfabeto. El decodificador JWT asume base64url porque el estándar lo exige.
Datos numéricos
- Longitud codificada: ambas variantes expanden el binario exactamente 4/3 (
ceil(n/3)*4). 100 bytes de entrada → 136 caracteres de salida en Base64 estándar, o 134 sin relleno en base64url. - Sobrecarga del relleno: el Base64 estándar añade 0, 1 o 2 caracteres
=por cadena codificada — como máximo una penalización del 1,5% para cargas cortas, insignificante para largas. - Coste de codificación porcentual en URLs: cada
+ / =en Base64 estándar se expande a 3 bytes (%2B %2F %3D) al codificarse en URL. Para un JWT de 512 caracteres, son típicamente 30-60 bytes desperdiciados — base64url los evita por completo. - Tamaño del alfabeto: 64 caracteres en ambos, por lo que cada carácter de salida lleva exactamente 6 bits de entrada.
- Tamaños de segmento JWT (RFC 7519): encabezado típico 40-50 caracteres, carga útil 100-400 caracteres, firma HS256 43 caracteres (exactamente 32 bytes / 6 = 43 caracteres base64url, sin relleno).
- Rendimiento:
Buffer.from(s, "base64")de Node 20 decodifica a ~2 GB/s;base64.b64decodede Python a ~600 MB/s en un portátil de 2024.
Matriz de decisión
| Dónde vive el valor | Elige |
|---|---|
| Ruta URL o cadena de consulta | base64url, sin relleno |
| JWT (encabezado / carga útil / firma) | base64url, sin relleno (RFC 7519) |
| Verificador PKCE OAuth 2.0 | base64url, sin relleno (RFC 7636 §4.1) |
| Cookie firmada (Rails / Django / Express) | base64url, sin relleno |
URI data: en HTML/CSS | Base64 estándar con relleno |
| Adjunto de correo MIME | Base64 estándar con relleno, ajuste de 76 caracteres (RFC 2045) |
| Cabecera HTTP Basic Auth | Base64 estándar (RFC 7617) |
| Claves de suscripción WebPush | base64url, sin relleno (RFC 8291) |
Fuentes
- RFC 4648 — Las codificaciones de datos Base16, Base32 y Base64 — rfc-editor.org/rfc/rfc4648 (§4 estándar, §5 seguro para URL).
- RFC 7519 — JSON Web Token (JWT), exige base64url sin relleno — rfc-editor.org/rfc/rfc7519.
- RFC 7636 — PKCE OAuth 2.0, §4.1 especifica desafío SHA-256 codificado en base64url — rfc-editor.org/rfc/rfc7636.
Frequently asked questions
- ¿Puedo decodificar base64url con un decodificador Base64 estándar?
- A veces — Buffer.from de Node es permisivo, b64decode de Python no lo es. El enfoque seguro entre lenguajes: reemplaza ‘-’ por ‘+’, ‘_’ por ‘/’, y rellena hasta múltiplo de 4 con ‘=’ antes de decodificar. Los entornos modernos ofrecen decodificadores base64url explícitos (Node 16+, urlsafe_b64decode de Python) que gestionan esto directamente.
- ¿Por qué base64url elimina el relleno?
- Por tres razones: es matemáticamente redundante (los decodificadores pueden inferir la longitud original a partir de la longitud de salida módulo 4), el carácter ‘=’ necesita codificación porcentual en URLs, y el ‘=’ final confunde a algunos analizadores de rutas, CDN y claves de caché.
- ¿Los segmentos de JWT son siempre base64url?
- Sí. El RFC 7519 exige base64url sin relleno para los tres segmentos del JWT (encabezado, carga útil, firma). Codificar un JWT con Base64 estándar produce un token que ninguna biblioteca conforme verificará.
- ¿Base64url ahorra bytes respecto a Base64 estándar?
- Marginalmente — el mismo tamaño de alfabeto implica la misma longitud codificada antes del relleno. Eliminar el relleno ahorra 0-2 bytes por codificación. El ahorro real proviene de evitar la codificación porcentual (cada %XX son 3 bytes por 1), que puede ahorrar 6-9 bytes por cadena Base64 en una URL.
Related
Published May 16, 2026