¿Tiene que ocuparse del formato Base64? Entonces esta página es perfecta para Ud. Utilice nuestra práctica herramienta en línea para codificar o decodificar sus datos.

Decodificación Base64 en Bash: una guía completa

De vez en cuando, una cadena de letras, dígitos y algún +, / o = de paso aterriza en tu terminal, y necesitas recuperar la cosa de verdad. Un JWT pegado en un ticket, un archivo .b64 adjunto a un correo de soporte, un secreto de Kubernetes que se lee como sopa de letras, un PNG escondido dentro de una etiqueta data: de HTML. Esta es la guía de campo para sacar los bytes de vuelta en Bash y la shell, usando las herramientas que casi seguro que ya tienes instaladas.

El formato en una respiración: Base64 reescribe cada tres bytes de datos crudos como cuatro caracteres tomados de un alfabeto de 64 letras (A-Z, a-z, 0-9, más + y /), y añade uno o dos signos = al final cuando el número de bytes no es un múltiplo de tres. Decodificar es la dirección que encoge en ese trueque: entran cuatro caracteres, salen tres bytes. La página de inicio de este sitio recorre el formato paso a paso, así que este artículo dedica su tiempo a donde corresponde, a la parte de la shell del trabajo.

Aquí viene el giro: no existe un único comando base64. El nombre lo comparten un programa en C de GNU, una reescritura en Rust, un applet de BusyBox, un vestigio de BSD y una utilidad de OpenSSL cuyos flags se solapan de una manera verdaderamente peligrosa. Todos se ponen de acuerdo en el alfabeto, pero no siempre en cómo se ve una entrada rota, y esa diferencia es donde los scripts van a morir. Así que el paso uno es descubrir quién responde cuando escribes base64.

Conoce el decodificador con el que hablas

Un solo comando cuenta la mayor parte de la historia:

base64 --version

Según la máquina, tienes delante una de estas familias:

Lo que ves Lo que tienes Flag de decodificación
base64 (GNU coreutils) 9.x La implementación en C clásica, todavía el predeterminado en la mayoría de las distribuciones Linux -d o --decode
base64 (uutils coreutils) 0.8.x La reescritura en Rust de coreutils, el userland predeterminado de las versiones actuales de Ubuntu -d (y, inusualmente, -D también funciona)
Texto de uso estilo BSD, sin flag de versión El base64 de BSD en macOS y los BSDs, descendiente de la vieja herramienta bintrans -D (en esta familia, la minúscula -d significa depurar, no decodificar)
BusyBox v1.x El binario todo en uno de Alpine Linux y de los sistemas embebidos -d

Fíjate en lo que falta de esa tabla: OpenSSL. openssl base64 es un animal completamente distinto, y su flag -d significa descifrar, no decodificar. Ese único flag es responsable de más archivos de salida silenciosamente vacíos que cualquier otro hábito de este artículo, así que lo conoceremos a fondo en la sección de alternativas.

Si tu distribución empaqueta más de una familia lado a lado (las versiones actuales de Ubuntu lo hacen), un par de comandos más te muestran el panorama completo:

command -v base64
base64 --version 2>&1 | head -1

Cuatro one-liners que cubren la mayoría de los días

Decodifica una cadena desde la entrada estándar. Este es el movimiento que harás mil veces, y el printf impide que la shell te decore el payload:

printf '%s' "SGVsbG8sIFdvcmxkIQ==" | base64 -d

Decodifica un archivo. Toda implementación seria acepta un argumento FILE, y es la manera más limpia de mantener los datos alejados del mecanismo de comillas de la shell:

base64 -d payload.b64 > payload.bin

Decodifica desde un here-string. El here-string añade un salto de línea al final, pero todos los decodificadores tratan los saltos de línea como espacio en blanco ignorable, así que esto es completamente seguro para blobs pequeños:

base64 -d <<< "SGVsbG8sIFdvcmxkIQ=="

Decodifica un blob envuelto en varias líneas con un heredoc. Poner el delimitador entre comillas simples impide que la shell interprete cualquier cosa dentro:

base64 -d <<'EOF'
SGVs
bG8s
IFdv
cmxk
IQ==
EOF

Los cuatro imprimen Hello, World!. En macOS y los BSDs, intercambia -d por -D en todos los ejemplos de arriba; el resto de la sintaxis es idéntico.

La entrada desordenada es la norma

El Base64 que encuentras en el mundo real rara vez es una línea limpia. Llega envuelto cada 76 caracteres (convención MIME) o cada 64 caracteres (convención PEM), exportado desde Windows con finales de línea CRLF, o copiado de una ventana de chat con espacios sueltos en medio. La buena noticia: al decodificador no le importa dónde estén los saltos de línea, siempre que sean saltos de línea de verdad.

La cura universal para cualquier estilo de envolvimiento es lavar los saltos de línea antes de decodificar:

tr -d '\r\n' < blob.b64 | base64 -d

Los retornos de carro son el caso especial. Un salto de línea es entrada permitida, pero un \r no es un salto de línea para los decodificadores estrictos. Un blob que cruzó por un sistema Windows tropieza al decodificador de GNU, que imprime un resultado parcial y luego falla; la solución es quitar los retornos de carro primero:

printf 'SGVs\r\nbG8s\r\n' | tr -d '\r' | base64 -d

Si ves fragmentos como Hel seguidos de un error, estás parado sobre un blob CRLF. El mismo lavado, tr -d '\r\n', antes de decodificar es el hábito portable para cualquier entrada que no hayas producido tú.

Para entradas verdaderamente corruptas, GNU y uutils ofrecen el flag -i (--ignore-garbage), que omite los caracteres fuera del alfabeto y decodifica lo que puede:

printf 'SGVs!bG8' | base64 -di

Eso imprime Hello. Antes de convertir -i en hábito por defecto, sabe por qué la norma advierte en contra: la sección 3.3 del RFC 4648 dice que las implementaciones deben rechazar datos que contengan caracteres fuera del alfabeto, porque los caracteres ignorados pueden explotarse como canal encubierto que contrabandea datos que nunca aparecen en la salida decodificada. Recurre a -i cuando un pegado desde un documento arrastró puntuación, no cuando estás verificando datos de los que te fías.

Así se comportan de verdad los tres decodificadores principales en los bordes, con las herramientas de 2026 (uutils 0.8.x, GNU coreutils 9.7, BusyBox 1.37):

Entrada uutils 0.8.x GNU 9.7 BusyBox 1.37
SGVsbG8sIFdvcmxkIQ==, limpia Hello, World!, salida 0 Hello, World!, salida 0 Hello, World!, salida 0
SGVsbG8s, ocho caracteres, sin relleno Hello,, salida 0 Hello,, salida 0 Hello,, salida 0
Zg, dos caracteres, sin relleno f, salida 0 f, salida 0 error, "truncated input"
SGV, tres caracteres, sin relleno error, sin salida He impreso, luego error error, "truncated input"
Líneas envueltas en CRLF decodifica bien, salida 0 salida parcial, luego error decodifica bien, salida 0
SGVs!bG8, puntuación suelta error (con -i: Hello) salida parcial (con -i: Hello) salida parcial (sin flag -i en absoluto)
SGV=, bits sobrantes no canónicos error, sin salida He impreso, luego error He, salida 0
TQ==junk, basura después del relleno salida 0, sigue decodificando junk salida 0, sigue decodificando junk M impreso, luego "truncated input" (salida 1)

Tres lecciones de esa tabla. Primera, no hay regla universal para las colas sin relleno: BusyBox quiere que la longitud sea múltiplo de cuatro, mientras que GNU y uutils aceptan los restos legales pero solo cuando los bits que sobran son todos cero (por eso Zg pasa y SGV no). Segunda, GNU y BusyBox escriben los bytes que ya decodificaron antes de fallar, así que un script que redirige a un archivo y comprueba el código de salida después se quedará encantado con un archivo medio decodificado. Comprueba siempre el estado de salida, y trata cualquier archivo dejado por una decodificación fallida con sospecha. Tercera, esa última fila: uutils y GNU siguen decodificando junk después del == y salen con 0, porque nada les dice que el flujo debió terminar; BusyBox es la excepción, imprime el único byte antes del relleno y luego falla con un error de entrada truncada. Si la basura al final te importa, valida la forma de la entrada antes de fiarte de la salida.

base64url: el alfabeto de tokens y URLs

La sección 5 del RFC 4648 define un segundo dialecto: la misma matemática de 6 bits, pero con - y _ sustituyendo a + y /, y el relleno eliminado, porque una URL rara vez necesita anunciar la longitud exacta en bytes. El RFC es tajante al respecto: esta codificación "no debe considerarse lo mismo que la codificación base64". Si alguna vez has mirado un JWT, ya te has cruzado con el dialecto, porque sus segmentos son base64url con el relleno retirado.

La receta de la shell es un intercambio en dos pasos: traduce los caracteres seguros para URL de vuelta a sus primos estándar, y luego decodifica:

printf '%s' "_k-C" | tr '_-' '/+' | base64 -d | xxd -p

Eso devuelve fe4f82, tres bytes crudos que por casualidad llevaban un traje de URL. El intercambio es posicional, así que la dirección importa: la codificación va tr '+/' '-_', la decodificación va tr '_-' '/+'. Confundirlas no produce error; simplemente genera bytes distintos en silencio, que es el peor tipo de bug.

Ahora la trampa que atrapa a quienes tratan base64url como Base64 plano. El relleno es opcional en el dialecto, y un segmento cuya longitud es tres módulo cuatro es exactamente la forma que los decodificadores estrictos escudriñan. El movimiento robusto es restaurar primero los caracteres = que faltan, y eso es una pequeña función:

b64url_decode () {
  local s=$1
  local n
  n=$(printf '%s' "$s" | wc -c | tr -d '[:space:]')
  while [ $(( n % 4 )) -ne 0 ]; do
    s="${s}="
    n=$(( n + 1 ))
  done
  printf '%s' "$s" | tr '_-' '/+' | base64 -d
}
b64url_decode "eyJzdWIiOiJob21lciJ9"

La última línea imprime {"sub":"homer"}, un objeto JSON sin adornos que un servidor web firmó o selló un momento antes. Los segmentos cuya longitud es uno módulo cuatro están malformados desde el principio, y ninguna cantidad de relleno los salva, así que el rechazo de la función a ese caso es una característica.

También hay un camino nativo en GNU coreutils: basenc, el hermano mayor de base64, entiende el dialecto directamente:

printf '%s' "Zg==" | basenc --base64url -d

Eso imprime f, el único byte escondido en dos caracteres. Un aviso antes de construir tuberías sobre él: el basenc de GNU de esta época hasta decodifica entrada base64url sin relleno (un Zg a secas) sin quejarse, mientras que el basenc de uutils todavía quiere que primero se restaure el relleno, como muestra el ejemplo de arriba. La pequeña función de arriba funciona en todas partes, y por eso es la elección portable.

Abriendo un JWT

Un JSON Web Token es tres segmentos base64url unidos por puntos, según el RFC 7515. Los dos primeros son JSON plano (la cabecera y las claims del payload), así que se decodifican directamente a texto legible. El tercero es la firma, un resumen binario crudo, así que déjalo estar: decodificarlo te da los bytes de la firma, no un mensaje.

token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiJ9.abc123"
b64url_decode "$(printf '%s' "$token" | cut -d. -f2)"

Eso imprime {"sub":"42"}, la claim subject, sin servidor de por medio. Lee las claims con la cabeza clara sobre lo que es y no es la decodificación: revela, pero no verifica. La firma no dice nada hasta que el servidor que tiene la clave la recomputa, que es un trabajo para openssl dgst (el artículo de codificación muestra la danza completa de acuñación), no para este. Un error frecuente de campo es tratar un payload decodificado como prueba de que un token es válido; un atacante puede acuñar a mano tokens sin firmar o con firma débil, y un decodificador los leerá a todos con entusiasmo.

Archivos, bytes y la muralla de las variables

La decodificación te entrega bytes crudos. Pueden deletrear una frase en inglés, o pueden ser el medio de un PNG, una librería compartida o un archivo zip. El archivo es el único lugar de un script de shell donde los bytes están completamente a salvo, así que el flujo de trabajo por defecto es decodificar a archivo y luego comparar:

base64 -d photo.b64 > photo.png

Cuando el original está a tu lado, la comparación byte a byte es la única prueba que importa:

cmp photo.png photo.png.orig && echo "byte-for-byte identical"

Cuando el original está lejos, compara sumas de verificación en su lugar:

sha256sum expected.bin
base64 -d blob.b64 | sha256sum

Dos hashes que coinciden y tu decodificación es exacta de forma demostrable, y eso gana a cualquier cantidad de mirar a ojo en un visor de imágenes o un volcado hexadecimal.

Las variables de la shell son otra historia, y son una muralla por dos razones. La sustitución de comandos $(...) quita todos los saltos de línea finales de la salida, y no puede contener ni un solo byte NUL: bash imprime una advertencia y los descarta en silencio. Un payload de tres bytes, 41 00 42, muestra los dos problemas en una sola demostración:

printf 'QQBC' | base64 -d > out.bin
xxd out.bin

El archivo contiene los tres bytes (41 00 42). El camino de la variable no:

v=$(printf 'QQBC' | base64 -d)
printf '%s' "$v" | wc -c

Te sale 2, con una advertencia en el error estándar sobre el byte nulo ignorado. La lección es corta: si los datos pueden ser binarios, decodifícalos a un archivo, inspecciónalos con xxd y no los pases nunca por una variable.

Dónde se esconde el Base64 en el trabajo real

Una vez que la decodificación se vuelve cómoda, empiezas a notar el formato por todas partes. Estas son las esquinas del trabajo real de la shell donde aparece, cada una con el movimiento exacto.

Secretos de Kubernetes. Cada campo bajo .data de un secreto es Base64, y la documentación oficial insiste en que esto es codificación, no cifrado. Leerlo de vuelta es un one-liner clásico:

kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d; echo

Parches binarios de Git. Cuando un diff toca archivos binarios, git diff --binary emite un bloque GIT binary patch. No vayas a base64 -d aquí: las líneas de ese bloque son la codificación de estilo base85 de git (cada línea empieza con un carácter de longitud, A-Z o a-z, seguido de datos base85), no Base64, y un decodificador plano se atragantará con ellas. La herramienta correcta es la propietaria del formato:

git diff --binary | grep -a -A2 'GIT binary patch'

Alimenta el diff a git apply o git am, y déjalos que hagan el desempaquetado.

Data URIs. Una imagen incrustada en HTML o CSS se parece a data:image/png;base64,iVBOR.... Quita todo hasta la coma incluida, lava los saltos de línea, decodifica, y ya tienes el archivo entre las manos:

cut -d, -f2- icon.uri | tr -d '\r\n' | base64 -d > icon.png

Blindaje PEM. Los certificados y las claves privadas envuelven su Base64 en líneas de marco que no son Base64 en absoluto. Selecciona el bloque blindado, descarta las dos líneas de marco y decodifica al binario DER crudo:

awk '/BEGIN CERTIFICATE/{f=1;next} /END CERTIFICATE/{f=0} f' cert.pem | tr -d '\r\n' | base64 -d > cert.der

Intercambia CERTIFICATE por PRIVATE KEY o la etiqueta que lleve tu archivo; la forma es la misma.

Correo MIME. Cualquier parte de un correo con Content-Transfer-Encoding: base64 viene envuelta cada 76 caracteres con finales de línea CRLF, porque eso es lo que prescribe el RFC 2045. La combinación portable es el lavado más la decodificación:

tr -d '\r\n' < attachment.b64 | base64 -d > attachment.bin

Pegados del portapapeles. El texto copiado de un navegador, una ventana de chat o un documento llega con espacios sueltos y un salto de línea final. Los espacios no son caracteres del alfabeto, así que los decodificadores estrictos rechazan el pegado; el rescate estándar es descartarlos primero:

tr -d ' \r\n' < pasted.b64 | base64 -d

Primero los bytes: conjuntos de caracteres y Unicode

El error más repetido en el trabajo con Base64 es pensar en caracteres cuando el formato solo conoce bytes. base64 -d te entrega bytes crudos, y si se convierten en texto legible es una decisión que toma lo que los lea a continuación. Esa decisión es un charset, y ocurre después de la decodificación, nunca dentro de ella.

UTF-8 es la suposición por defecto y normalmente la correcta. La palabra café en UTF-8 ocupa cinco bytes, y la decodificación es un placer:

printf 'Y2Fmw6k=' | base64 -d | xxd -p

Eso es 636166c3a9: caf más los dos bytes UTF-8 c3 a9 que forman la é. Los sistemas más viejos, en cambio, te entregarán bytes Latin-1 (ISO-8859-1), donde la misma letra es el único byte e9. Decodificar un blob así e imprimirlo tal cual en un terminal UTF-8 te da un carácter destrozado; la solución es reinterpretar los bytes con iconv antes de que nada más los vea:

base64 -d latin1.b64 | iconv -f ISO-8859-1 -t UTF-8 > utf8.txt

Unos cuantos hechos a nivel de byte que ahorran tiempo real de depuración:

  • Un BOM UTF-8 son los tres bytes ef bb bf, que codificados dan 77u/. Fíjate en la /, un carácter hostil para URLs, que es exactamente el tipo de cosa que base64url existe para arreglar. Si un archivo decodificado "tiene basura invisible al frente", revisa los tres primeros bytes con xxd.
  • Un emoji como 😀 ocupa cuatro bytes UTF-8 y se convierte en ocho caracteres Base64, 8J+YgA==. El + de ahí es el alfabeto estándar funcionando exactamente como fue diseñado; con ropa base64url se convierte en 8J-YgA.
  • Las secuencias UTF-8 inválidas se decodifican perfectamente bien como bytes y luego se muestran como basura o caracteres de reemplazo. Eso no es un fallo de Base64; la decodificación hizo su trabajo. xxd o hexdump -C te muestran lo que los bytes son de verdad.
  • El locale de tu terminal decide cómo renderiza esos bytes. "El terminal muestra basura" es una afirmación sobre la pantalla, no sobre los datos. Los bytes no cambiaron en el viaje.

Blobs grandes: flujos, divisiones y velocidad

Base64 es un formato de flujo, y el decodificador funciona como una tubería de streaming de verdad: un archivo de 10 GB nunca se queda sentado en memoria; solo pasa a través. Eso hace que el lado de la decodificación sea casi aburrido a escala, que es exactamente lo que quieres.

Cuando un blob grande se dividió en trozos para transferirlo (un límite de correo, un tamaño de adjunto de ticket, un mensaje de mensajería), reensamblar es solo un cat en el orden correcto, seguido del lavado de siempre:

cat part_* | tr -d '\r\n' | base64 -d > big.bin

Mantén un modelo mental para los tamaños: la forma codificada siempre es unos tercios más grande que el original, cuatro caracteres por cada tres bytes. Así que cuando alguien te diga que el archivo .b64 debería tener el mismo tamaño que la cosa que esconde, se equivoca, y ahora puedes decirle en cuánto: un payload de 300 MB llega como unos 400 MB de texto.

La velocidad no es una preocupación en la práctica. Estos son bucles dirigidos por tablas sobre memoria plana, y en una máquina moderna un blob de 200 MB se decodifica en aproximadamente una décima de segundo con GNU, e incluso la más lenta de las implementaciones comunes (BusyBox) solo es unas pocas veces más lenta, y sigue siendo muy inferior a dos segundos. El uso de memoria se mantiene plano por grande que sea la entrada, porque nada se bufferiza.

Cuando la máquina no tiene base64

La mayoría de los sistemas tienen al menos una de las herramientas de arriba, y la mayoría tiene varias. Si tu plataforma carece por completo de coreutils (un container recortado, un dispositivo inusual), los caminos de instalación se ven así:

Plataforma Cómo conseguirla Notas
Debian / Ubuntu preinstalado; apt install coreutils si el sistema fue recortado Las versiones actuales de Ubuntu ponen la familia uutils por defecto (desde 25.10), con el gemelo GNU accesible como gnubase64; Debian 13 todavía empaqueta GNU coreutils por defecto
RHEL / Fedora dnf install coreutils preinstalado en prácticamente todas las imágenes
Alpine apk add busybox (normalmente ya está) busybox base64 -d; sin flag -i
macOS integrado; brew install coreutils para la versión GNU flags de BSD: -D para decodificar, -b para el ancho de línea; brew te da gbase64

Y si ni un gestor de paquetes es una opción, las alternativas universales de abajo todas leen de la entrada estándar y escriben bytes a la salida estándar, así que encajan en las mismas tuberías:

openssl base64 -d -A -a < in.b64 > out.bin
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < in.b64 > out.bin
python3 -c 'import base64, sys; sys.stdout.buffer.write(base64.b64decode(sys.stdin.buffer.read()))' < in.b64 > out.bin

En un sistema BusyBox con el applet base64 compilado fuera, la vieja guardia sigue funcionando: uuencode -m produce Base64 MIME, y su hermano lo lee de vuelta:

busybox uudecode -o out.bin in.uu

Trampas que se comen tardes enteras

Todo lo de abajo es comportamiento que encontrarás en el campo, reunido en un solo lugar:

Trampa Qué pasa La solución
openssl base64 -d por sí solo No hace nada en silencio y sale con 0, porque allí -d significa Descifrar openssl base64 -d -A -a, y trata un resultado de cero bytes como un error
Decodificar en macOS con -d El base64 de BSD rechaza el flag (o lo trata como depuración) -D, o instala coreutils para tener gbase64
Finales de línea CRLF en la entrada GNU imprime un resultado parcial y luego falla; uutils y BusyBox lo aceptan tr -d '\r\n' primero, siempre para entradas ajenas
Colas sin relleno BusyBox rechaza cualquier resto; GNU y uutils solo aceptan colas canónicas restaurar el relleno = que falta antes de decodificar
Basura después del relleno, p. ej. TQ==junk uutils y GNU siguen decodificando y salen con 0; BusyBox da error validar la forma de la entrada antes de fiarse de la salida
Bits sobrantes no canónicos, p. ej. SGV= uutils y GNU rechazan (GNU tras la salida parcial); BusyBox decodifica igualmente la entrada está corrupta; regenerar la codificación aguas arriba
Leer la salida decodificada en una variable $(...) quita los saltos de línea finales y no puede contener bytes NUL en absoluto decodificar a un archivo, inspeccionar con xxd
-i con entrada no confiable la corrupción se vuelve un éxito silencioso; los caracteres ignorados pueden llevar datos ocultos decodificar en modo estricto, leer el error, arreglar la fuente
Confundir los dos alfabetos decodificar base64url como estándar (o al revés) da bytes erróneos o un error conocer el formato antes de decodificar; el intercambio es tr '_-' '/+'
Tratar un secreto decodificado como secreto un solo comando lo deshace; el RFC documenta incidentes reales de credenciales filtradas cifrado de verdad, no codificación

La fila de OpenSSL merece un párrafo propio, porque el fallo es tan silencioso. En OpenSSL 3.x el -d de la aplicación independiente es su opción general de "descifrado", y el procesamiento Base64 es un modo aparte que se selecciona con -a. Así que openssl base64 -d lee tu entrada, no hace nada, no imprime nada y sale con 0. La decodificación que funciona es openssl base64 -d -A -a, donde -A le dice que la entrada es una línea continua. Si tienes que usar OpenSSL para decodificar, trata una salida de cero bytes como un error, cada vez sin excepción.

Una rutina bien apretada

Los hábitos para que Base64 nunca te saque con la suya:

  • Falla a lo grande. Ejecuta los scripts con set -euo pipefail y comprueba los códigos de salida. Todos los decodificadores comunes salen con 1 ante entrada mala; OpenSSL es el ruidoso que se calló, así que con él también comprueba que la salida no esté vacía.
  • Demuestra el viaje de ida y vuelta. cmp o sha256sum entre los bytes esperados y los restaurados es la única prueba que importa. Nunca mires binario a ojo.
  • Binario a archivos, nunca a variables. Los saltos de línea finales y los bytes NUL son ambas víctimas de la sustitución de comandos.
  • Lava la entrada ajena. tr -d ' \r\n' antes de decodificar cualquier cosa que cruzó una frontera de plataforma.
  • Quédate estricto por defecto. Mantén -i apagado hasta que hayas visto qué fue exactamente lo que falló; un fallo estricto te dice el lugar, uno indulgente no te dice nada.
  • Nombra el alfabeto. El Base64 estándar y base64url son codificaciones distintas según el RFC 4648. Decodifica un JWT como base64url, un adjunto de correo como estándar, y no intercambies caracteres sin saber por qué.
  • Nunca imprimas lo que acabas de decodificar. Todo el sentido del formato en el mundo de los secretos es la invisibilidad; todo el sentido de un log es la visibilidad. Esos dos objetivos no se mezclan.

Un pequeño shim portable para la pregunta de "¿qué flag quiere esta máquina?":

case "$(base64 --version 2>&1 | head -2)" in
  *uutils*|*GNU*|*BusyBox*) b64d () { base64 -d; } ;;
  *)                        b64d () { base64 -D; } ;;
esac
b64d < in.b64 > out.bin

La instrucción case atrapa a las familias GNU, uutils y BusyBox por su lema de versión - las tres aceptan el flag en minúsculas - y recurre al flag de BSD para cualquier otra cosa, que es exactamente la división en cuatro del mundo real: GNU, uutils, BusyBox, BSD.

Cómo la shell aprendió a decodificar

El formato es viejo; el comando que estás escribiendo, no. Una línea de tiempo breve del lado shell de la historia:

  • 1980, Berkeley. Mary Ann Horton escribe uuencode y uudecode en la Universidad de California, Berkeley, para llevar archivos binarios (normalmente comprimidos) a través del correo. El nombre significa "codificación de Unix a Unix", una codificación segura para mover archivos entre sistemas Unix que quizá no compartan un charset. Durante décadas esto, no el Base64, es lo que los usuarios de shell buscan.
  • La era del módem. Las primeras codificaciones base cabalgan sobre el mismo problema: uuencode en UNIX, BinHex en el TRS-80 y el Apple II con el Macintosh un paso atrás, cada una asumiendo solo los caracteres que su propio terminal podía imprimir.
  • 1993. MIME estandariza Base64 para el correo (RFC 1521, más tarde RFC 2045), con el envolvimiento de líneas cada 76 caracteres que todavía define el predeterminado de base64 hoy en día.
  • 2003 y octubre de 2006. El RFC 3548 pone orden en la familia, y el RFC 4648 lo sustituye con los alfabetos y la regla de decodificación estricta que este artículo sigue citando, incluido base64url.
  • 15 de agosto de 2006. coreutils 6.0 añade el propio comando base64, y su archivo NEWS lo acredita sin rodeos como "funcionalidad de codificación y decodificación base64 (RFC 3548)". Antes de esa fecha, los usuarios de shell en Linux recurrían a openssl base64, uuencode -m, Perl o Python, y por eso tantos scripts viejos asumen que OpenSSL es la única opción en el pueblo.
  • OS X 10.7. macOS empaqueta su propio base64, el sabor BSD con el flag -D y sin envolvimiento de líneas por defecto, que es de donde viene la división entre -d y -D.
  • Marzo de 2024. coreutils 9.5 relaja el decodificador: el relleno ya no es obligatorio al decodificar, y las codificaciones con bits sobrantes distintos de cero ahora se diagnostican como corrupción en vez de aceptarse en silencio.
  • 2025. La reescritura en Rust de coreutils (uutils) se vuelve el predeterminado de las versiones actuales de Ubuntu. Mismo nombre de comando, mismos flags, un motor nuevo con sus propias opiniones sobre los bordes, como aceptar -D como alias.

Curiosidades que valen la pena guardar

  • El comando es más joven que el formato. Base64 lleva en el correo desde 1993, pero el comando base64 solo apareció en 2006. Trece años de scripts de shell hicieron este trabajo con otras herramientas, y todavía puedes encontrar sus huellas por todas partes.
  • Un fósil en el texto de ayuda. El base64 de uutils todavía describe su alfabeto como "RFC 3548" en su ayuda, el predecesor jubilado del RFC 4648, mientras que su gemelo GNU ya cita el estándar vigente. Un fósil diminuto, visible solo si lees la ayuda.
  • El no-op silencioso más caro de la caja de herramientas. openssl base64 -d no hace nada y sale con 0. Una hora entera de depuración, perdida, y la prueba del código de salida pasó.
  • BusyBox se queda con los bits sobrantes. Decodificará SGV= encantado, cuyos bits restantes son distintos de cero y por lo tanto no canónicos, mientras que su primo GNU se enfada con la misma entrada. Mismo RFC, nervios distintos.
  • El nombre del formato es verdad en todas las máquinas. printf 'base64' | base64 da YmFzZTY0 en GNU, uutils, BusyBox y OpenSSL por igual. Ha sido verdad desde 2006 y siempre lo será.
  • Base85 no es Base64. Los bloques "binary patch" de Git parecen Base64 al ojo no entrenado, pero las líneas prefijadas por longitud son un dialecto de estilo base85. El impostor que cuesta a la gente un desvío de grep-y-decodificar.
  • Once caracteres, sesenta y cuatro bits. Un ID de vídeo de YouTube es una cadena base64url de 11 caracteres, un número de 64 bits con ropa de URL, y por eso puede aparecer en una URL sin un solo signo de porcentaje.
  • Los decodificadores discrepan en un carácter. Una sola cadena como SGV= divide al campo en tres bandos, como muestra la tabla de comportamiento de arriba. Si una decodificación falla en una entrada "evidentemente válida", probablemente estás parado en la línea de los bits sobrantes, y la codificación nunca fue canónica desde el principio.

Así que la próxima vez que una cadena de letras, dígitos, más y barra aterrice en tu terminal, conoces la historia entera: qué decodificador está mirando, qué alfabeto está hablando, dónde se esconden los saltos de línea, y exactamente cómo sacar los bytes de vuelta, intactos y demostrados byte a byte, sin perder una hora con un retorno de carro. Y cuando el trabajo apunta al otro lado, empacando tu propio binario en un sobre de texto para el viaje, el artículo relacionado de codificación Base64 enlazado abajo cubre ese ritual con la misma profundidad.

Última actualización: 2026-09-08

Artículo relacionado: Codificación Base64 en Bash: una guía completa