El problema que resuelve Base64
Los primeros sistemas de correo electrónico se diseñaron para transportar texto en ASCII de 7 bits. Cualquier byte con un valor por encima de 127, o un carácter de control en el lugar equivocado, podía alterarse o perderse en algún servidor intermedio. Cuando surgió la necesidad de enviar archivos adjuntos (imágenes, documentos, programas, que son secuencias arbitrarias de bytes), hizo falta una forma de disfrazar bytes de texto inofensivo. El estándar MIME (RFC 2045, de 1996) adoptó Base64 para eso, y el mismo truco se volvió la solución estándar cada vez que hay que meter datos binarios en un canal pensado para texto: cabeceras HTTP, JSON, XML, URL, archivos de configuración.
Esta guía muestra cómo funciona Base64 bit a bit con ejemplos resueltos, por qué aparecen los signos = al final, qué variantes existen y cuáles son los errores más comunes al usarlo desde el código. Para codificar o decodificar un valor concreto, el codificador Base64 lo hace en el navegador.
Codificar a mano: "Man" → "TWFu"
El ejemplo clásico es la palabra Man. Son tres caracteres ASCII, es decir, tres bytes:
| Carácter | Decimal | Binario |
|---|---|---|
| M | 77 | 01001101 |
| a | 97 | 01100001 |
| n | 110 | 01101110 |
Base64 pone los 24 bits uno detrás del otro y los vuelve a cortar, esta vez en cuatro grupos de 6 bits:
01001101 01100001 01101110 ← 3 bytes (8 bits cada uno)
010011 010110 000101 101110 ← 4 grupos de 6 bits
19 22 5 46 ← valor de cada grupo
T W F u ← símbolo del alfabeto
Cada valor de 0 a 63 se traduce con el alfabeto: A–Z son 0–25, a–z son 26–51, 0–9 son 52–61, y + y / son 62 y 63. El 19 es T; el 22, W; el 5, F; y el 46 cae en las minúsculas: 46 − 26 = 20, y la letra en la posición 20 contando desde la a como 0 es la u. Resultado: TWFu.
Si se quieren comprobar las conversiones de binario a decimal de cada grupo, el conversor de bases muestra cualquier valor en las cuatro bases a la vez.
El relleno: por qué aparecen = y ==
El método anterior necesita bloques completos de 3 bytes. Cuando la entrada no es múltiplo de 3, el último bloque queda incompleto y se resuelve así: se agregan bits en cero hasta completar el último grupo de 6, y los caracteres que faltan para llegar a 4 se reemplazan por =.
Dos bytes, "Ma": son 16 bits. Se agregan dos ceros para llegar a 18 (tres grupos de 6) y se completa con un =:
01001101 01100001 00 ← 16 bits + 2 ceros de relleno
010011 010110 000100 ← 3 grupos
T W E = ← TWE=
Un byte, "M": son 8 bits. Se agregan cuatro ceros para llegar a 12 (dos grupos) y se completa con ==:
01001101 0000 ← 8 bits + 4 ceros de relleno
010011 010000 ← 2 grupos
T Q = = ← TQ==
La regla es fácil de recordar: == significa que sobró 1 byte, = que sobraron 2, y sin relleno, que la entrada era múltiplo de 3. El relleno no agrega información; solo deja la longitud en un múltiplo de 4, y por eso muchas implementaciones lo aceptan ausente y algunas variantes directamente lo omiten.
Decodificar a mano
El proceso inverso es simétrico: cada carácter se traduce a su valor de 6 bits, se concatenan y se cortan en bytes de 8. Con TWFu:
T=19 W=22 F=5 u=46
010011 010110 000101 101110
01001101 01100001 01101110 → 77 97 110 → "Man"
Si hay =, se descartan los bits de relleno sobrantes. Un detalle útil para detectar errores: una cadena Base64 cuyo largo (sin contar espacios) deja resto 1 al dividirla por 4 es necesariamente inválida, porque un solo carácter aporta 6 bits y no alcanza para formar un byte.
Cuánto ocupa
Cada 3 bytes se convierten en 4 caracteres, así que el tamaño crece un 33 % (4/3). En formatos que cortan el texto en líneas, como MIME con 76 caracteres por línea más el salto \r\n, el crecimiento real ronda el 37 %. Por eso Base64 no es buena idea para transportar archivos grandes dentro de JSON: una API que recibe documentos de varios megas suele funcionar mejor con multipart/form-data o con una subida directa del archivo binario.
Las variantes
Todas usan el mismo mecanismo de 6 bits; cambian el alfabeto o el formato:
| Variante | Definida en | Diferencia |
|---|---|---|
| Base64 estándar | RFC 4648, sección 4 | Alfabeto con + y /, relleno con = |
| base64url | RFC 4648, sección 5 | - y _ en lugar de + y /; relleno normalmente omitido |
| MIME | RFC 2045 | Estándar, con líneas de 76 caracteres como máximo |
| PEM | RFC 7468 | Estándar, con líneas de 64 caracteres entre -----BEGIN …----- y -----END …----- |
La variante base64url existe porque +, / y = tienen significado en las URL. Un token en Base64 estándar puesto en una query string sin más puede llegar alterado: muchos servidores interpretan el + como un espacio.
Dónde aparece en la práctica
Autenticación HTTP Basic. El RFC 7617 usa como ejemplo el usuario Aladdin con la contraseña open sesame. La cabecera que envía el navegador es:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Ese valor es Aladdin:open sesame en Base64, nada más. Cualquiera que intercepte la petición lee la contraseña al instante, y por eso Basic solo es aceptable sobre HTTPS.
JSON Web Tokens. Un JWT tiene tres partes separadas por puntos. La primera, en un token típico, es eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9, que en base64url decodifica a {"alg":"HS256","typ":"JWT"}. La cabecera y el contenido de un JWT son públicos para quien tenga el token; lo único que garantiza la firma es que nadie los modificó.
Data URIs. data:image/svg+xml;base64,PHN2Zy... permite incrustar un archivo dentro de HTML o CSS. Funciona bien para íconos pequeños; para imágenes grandes, el 33 % extra y la imposibilidad de cachearlas por separado suelen salir caros.
Base64 desde el código
| Entorno | Codificar texto UTF-8 | Decodificar |
|---|---|---|
| Node.js | Buffer.from(texto, "utf8").toString("base64") | Buffer.from(b64, "base64").toString("utf8") |
| Node.js (base64url) | Buffer.from(texto).toString("base64url") | Buffer.from(b64, "base64url") |
| Python | base64.b64encode(texto.encode()).decode() | base64.b64decode(b64).decode() |
| Python (base64url) | base64.urlsafe_b64encode(datos) | base64.urlsafe_b64decode(b64) |
| Bash | printf '%s' "$texto" | base64 | base64 -d |
En el navegador, btoa() y atob() existen desde hace décadas pero trabajan con "cadenas binarias", donde cada carácter representa un byte. Con texto que no sea ASCII fallan o dan un resultado distinto del esperado, así que primero hay que convertir el texto a bytes con TextEncoder.
Errores frecuentes
- Usarlo como si fuera cifrado. Guardar contraseñas, claves de API o datos personales "en Base64" no los protege en absoluto. Es tan reversible como leer un número en hexadecimal.
- El salto de línea de
echo.echo 'Hola' | base64codificaHola\ny produceSG9sYQo=. Conprintfoecho -nse obtieneSG9sYQ==. Es la causa más común de "mi Base64 no coincide". - Latin-1 en lugar de UTF-8.
btoa("ñ")devuelve8Q==, que es la ñ en Latin-1. En UTF-8, que es lo que espera casi cualquier sistema actual, la ñ esw7E=. Ybtoa("€")directamente lanza unInvalidCharacterError. - Quitar el relleno y no volver a ponerlo. Muchos decodificadores estrictos, como
base64.b64decodede Python, fallan conIncorrect paddingsi falta el=. Al recibir base64url sin relleno, hay que completarlo hasta un múltiplo de 4 antes de decodificar. - Mezclar variantes. Un decodificador estándar estricto rechaza
-y_, y uno de base64url rechaza+y/. Cuando la fuente no está clara, conviene normalizar antes. - Codificar dos veces.
SGVsbG8=codificado otra vez daU0dWc2JHOD0=. Si un valor decodificado sigue pareciendo Base64, probablemente se codificó dos veces en algún punto del camino.
Resumen
Base64 convierte cada 3 bytes en 4 caracteres de un alfabeto de 64 símbolos, de modo que cualquier dato binario pueda viajar por un canal de texto. El relleno = completa el último bloque, base64url cambia dos símbolos para que el resultado sea seguro en URL, y el costo es un 33 % de tamaño extra. No protege nada: es transporte, no seguridad.