Base64 vs Base64URL: qué cambia y cuándo usar cada uno

Base64URL es el Base64 estándar con dos caracteres sustituidos, - por + y _ por /, y normalmente sin el relleno =.

Las dos codificaciones se definen en el RFC 4648: el Base64 estándar en la sección 4 y la variante «URL and filename safe» (segura para URL y nombres de archivo), base64url, en la sección 5. Ambas dividen los datos en grupos de 6 bits exactamente de la misma manera y comparten 62 de sus 64 caracteres. Solo cambian los caracteres de los valores 62 y 63, además de la convención sobre el relleno. Por eso convertir de una a otra consiste en intercambiar caracteres, sin volver a codificar los datos, y por eso confundirlas es un error fácil que a menudo pasa desapercibido.

Los dos alfabetos, lado a lado

Base64 (RFC 4648, sección 4)Base64URL (RFC 4648, sección 5)
Valores del 0 al 61A-Z a-z 0-9A-Z a-z 0-9
Valor 62+-
Valor 63/_
Relleno con =Obligatorio, salvo que el formato que lo usa indique otra cosaNormalmente se omite
Seguro tal cual en URL y nombres de archivoNoSí

Los mismos bytes en las dos formas:

Bytes       fb ff bf
Base64      +/+/
Base64URL   -_-_

Text        <<???>>
Base64      PDw/Pz8+Pg==
Base64URL   PDw_Pz8-Pg

Los datos binarios codificados, como hashes, claves y tokens aleatorios, contienen muy a menudo un + o una / en algún punto, así que la diferencia no es un caso marginal. El propio RFC 4648 dice que base64url no debe considerarse la misma codificación que base64, y que el código debe indicar qué variante espera.

Por qué el Base64 estándar se rompe en URL y nombres de archivo

Cada uno de los tres caracteres adicionales del Base64 estándar significa algo en otro contexto:

  • + se lee como un espacio cuando una query string se decodifica como datos de formulario, que es lo que hacen URLSearchParams, $_GET de PHP y la mayoría de los frameworks de servidor. Un token como ab+c llega como ab c.
  • / separa los segmentos de una ruta, así que un valor Base64 en una ruta crea segmentos adicionales, y algunos servidores web rechazan la forma codificada %2F en las rutas. Tampoco se permite en nombres de archivo en Windows, macOS ni Linux.
  • = separa el nombre de un parámetro de su valor.

Codificar el valor en porcentaje (%2B, %2F, %3D) funciona cuando todas las piezas de código implicadas lo hacen exactamente una vez. En la práctica, alguna capa se olvida o lo hace dos veces, y por eso los formatos pensados para URL usan Base64URL y evitan por completo el escapado.

Reglas de relleno

Base64 codifica 3 bytes como 4 caracteres. Cuando la longitud de los datos no es múltiplo de 3, el último grupo es más corto, y el Base64 estándar lo completa hasta 4 caracteres con =:

Bytes en el último grupoBase64Base64URL sin relleno
1, por ejemplo ff/w==_w
2, por ejemplo fb ff+/8=-_8
3, por ejemplo fb ff bf+/+/-_-_

La sección 3.2 del RFC 4648 exige el relleno salvo que la especificación que hace referencia a él diga explícitamente lo contrario. La sección 5 añade que = normalmente tendría que codificarse en porcentaje dentro de una URL, y que el relleno puede omitirse cuando la longitud de los datos ya se conoce por otra vía. Los formatos basados en Base64URL aprovechan esa opción: JSON Web Signature (RFC 7515), y por tanto los JWT, elimina todos los = finales, y lo mismo hacen los code challenges de PKCE (RFC 7636).

Sin relleno, n bytes producen ⌈4n/3⌉ caracteres: 16 bytes se convierten en 22 caracteres en lugar de 24, y 32 bytes en 43 en lugar de 44. Una longitud que deja un resto de 1 al dividirla entre 4 nunca es válida en ninguna de las dos variantes.

Dónde se usa cada uno

ContextoVariante
Encabezado, payload y firma de un JWTBase64URL sin relleno
Valores de JSON Web Key como n y eBase64URL sin relleno
code_challenge de OAuth PKCEBase64URL sin relleno
Data URI, data:image/png;base64,...Base64 estándar (RFC 2397)
Adjuntos de correo (MIME)Base64 estándar, en líneas de 76 caracteres como máximo (RFC 2045)
Certificados y claves PEMBase64 estándar, en líneas de 64 caracteres (RFC 7468)
Cabecera de autenticación HTTP BasicBase64 estándar (RFC 7617)
Tokens en URL, nombres de archivo, claves de cachéBase64URL es la opción segura

Las bibliotecas usan por defecto el Base64 estándar: btoa, b64encode de Python, base64_encode de PHP y Base64.getEncoder() de Java producen +, / y relleno. Base64URL hay que pedirlo explícitamente.

Cómo convertir de uno a otro

atob y btoa solo conocen el Base64 estándar, así que con ellas tiene que convertir los caracteres y ajustar el relleno usted mismo:

// Base64 -> Base64URL
const url = b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

// Base64URL -> Base64
let std = url.replace(/-/g, '+').replace(/_/g, '/');
std += '='.repeat((4 - std.length % 4) % 4);

Los navegadores recientes también ofrecen Uint8Array.prototype.toBase64({ alphabet: 'base64url', omitPadding: true }) y Uint8Array.fromBase64(s, { alphabet: 'base64url' }), que trabajan directamente con bytes; compruebe su compatibilidad en los navegadores a los que se dirige antes de depender de ellas. Otras plataformas tienen soporte directo o una fórmula breve:

// Node.js
Buffer.from(data).toString('base64url');     // no padding
Buffer.from(s, 'base64url');

# Python
base64.urlsafe_b64encode(data).rstrip(b'=')
base64.urlsafe_b64decode(s + '=' * (-len(s) % 4))

// PHP
rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
base64_decode(strtr($s, '-_', '+/'), true);

// Java
Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
Base64.getUrlDecoder().decode(s);

urlsafe_b64decode de Python rechaza la entrada sin relleno, de ahí los signos = añadidos. base64_decode de PHP acepta que falte el relleno, incluso en modo estricto.

Trampas habituales

  • Errores que superan las pruebas. Un decodificador estándar solo falla con una entrada Base64URL que contenga - o _. Con datos aleatorios, eso ocurre en aproximadamente la mitad de los valores de 16 bytes y en unas tres cuartas partes de los de 32 bytes, como los resúmenes SHA-256 (1 − (62/64) elevado a 42 ≈ 74%). Una prueba con uno o dos valores de ejemplo puede pasar sin problemas. Por qué los decodificadores rechazan estas cadenas, y lo distinto que reacciona cada lenguaje, se explica en Cadena Base64 no válida.
  • Los decodificadores permisivos ocultan la confusión. Buffer.from(s, 'base64') de Node.js acepta los dos alfabetos, lo cual es cómodo, pero hace que el error solo aflore en otro servicio. b64decode de Python, en su modo por defecto, descarta en silencio - y _, y luego devuelve bytes erróneos o un error engañoso sobre el número de caracteres.
  • Base64URL no es codificación de URL. Codificar en porcentaje una cadena Base64 estándar da %2B, %2F y %3D; no la convierte en Base64URL, y un decodificador que espera una de las formas no aceptará la otra.
  • Ninguna de las dos variantes oculta nada. El payload de un JWT no es más que JSON codificado en Base64URL, legible para cualquiera que tenga el token. Pegue uno en el decodificador JWT para ver su encabezado y su payload.

El codificador Base64 de este sitio produce Base64 estándar con relleno; aplique la sustitución anterior si necesita Base64URL. Para decodificar, Decodificar Base64 acepta los dos alfabetos y añade por sí mismo el relleno que falte, así que puede pegar cualquiera de las dos formas.

Preguntas Frecuentes

¿Qué diferencia hay entre Base64 y Base64URL?

Base64URL sustituye + por - y / por _, de modo que el resultado es seguro en URL y nombres de archivo, y normalmente omite el relleno =. Los otros 62 caracteres y la forma de dividir los datos en grupos de 6 bits son idénticos.

¿Puede un decodificador Base64 normal leer Base64URL?

Solo después de convertirlo: sustituya - por + y _ por /, y añada = hasta que la longitud sea múltiplo de 4. Algunos decodificadores, como Buffer de Node.js, aceptan directamente los dos alfabetos.

¿Por qué los JWT usan Base64URL sin relleno?

Los tokens viajan en URL, cabeceras HTTP y cookies, donde +, / y = causan problemas. El RFC 7515 define la codificación de JSON Web Signature como base64url sin ninguno de los caracteres = finales.

¿Base64URL es más largo que Base64?

No. Los dos producen los mismos caracteres por byte; sin relleno, Base64URL es hasta dos caracteres más corto.