La decodificación Base64 en Go aparece constantemente — inspección de JWT, adjuntos de archivos binarios, payloads de API de servicios cloud. El paquete estándar encoding/base64 de Go lo maneja todo, pero elegir la variante de codificación incorrecta (estándar vs URL-safe, con o sin padding) es la causa más común de errores “illegal base64 data”. Esta guía cubre StdEncoding, URLEncoding, RawURLEncoding, decodificación en streaming con base64.NewDecoder, inspección del payload JWT, y cuatro errores que casi todo el mundo comete la primera vez. Para decodificaciones puntuales en el navegador, el decodificador Base64 de ToolDeck resuelve el problema al instante sin escribir ni una línea de código.
- ✓encoding/base64 forma parte de la librería estándar de Go — no requiere go get
- ✓Usa RawURLEncoding para tokens JWT y la mayoría de las APIs modernas (sin padding, alfabeto URL-safe)
- ✓StdEncoding usa + y / con padding =; URLEncoding cambia a - y _ pero mantiene el padding
- ✓base64.NewDecoder envuelve cualquier io.Reader para decodificación en streaming sin cargar en memoria
- ✓Verifica siempre el error retornado — un padding inválido o un alfabeto incorrecto produce "illegal base64 data"
¿Qué es la decodificación Base64?
La codificación Base64 representa datos binarios como texto ASCII usando 64 caracteres imprimibles (A–Z, a–z, 0–9, más dos extras). Decodificar revierte este proceso — convierte esa representación ASCII de vuelta a los bytes originales. Cada 4 caracteres Base64 decodifican exactamente 3 bytes. El esquema existe porque muchas capas de transporte (email, cabeceras HTTP, campos JSON) están diseñadas para texto, no para binario puro. Así se ve el ciclo completo de ida y vuelta:
package main
import (
"encoding/base64"
"fmt"
)
func main() {
// Raw bytes → Base64 encoded → decoded back to raw bytes
original := []byte("service_token:xK9mP2qR")
// Encoded: "c2VydmljZV90b2tlbjp4SzltUDJxUg=="
encoded := base64.StdEncoding.EncodeToString(original)
decoded, _ := base64.StdEncoding.DecodeString(encoded)
fmt.Println(string(decoded) == string(original)) // true
}Decodificar Base64 en Go con encoding/base64
El paquete encoding/base64 viene incluido con Go — sin dependencias externas. Expone cuatro variantes de codificación predefinidas como variables de nivel de paquete. La función más usada para entrada de tipo string es DecodeString, que retorna un slice de bytes y un error.
package main
import (
"encoding/base64"
"fmt"
"log"
)
func main() {
// Standard Base64 — the alphabet uses + and / with = padding
encoded := "eyJob3N0IjoiZGItcHJvZC51cy1lYXN0LTEiLCJwb3J0Ijo1NDMyfQ=="
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
log.Fatalf("decode error: %v", err)
}
fmt.Println(string(decoded))
// {"host":"db-prod.us-east-1","port":5432}
}El método Decode opera sobre slices de bytes en lugar de strings, y escribe el resultado en un buffer de destino preasignado. Debes dimensionar el buffer correctamente — usa base64.StdEncoding.DecodedLen(len(src)) para obtener el tamaño máximo (puede ser algunos bytes mayor que la longitud decodificada real debido al padding).
package main
import (
"encoding/base64"
"fmt"
"log"
)
func main() {
src := []byte("eyJob3N0IjoiZGItcHJvZCIsInBvcnQiOjU0MzJ9")
dst := make([]byte, base64.RawStdEncoding.DecodedLen(len(src)))
n, err := base64.RawStdEncoding.Decode(dst, src)
if err != nil {
log.Fatalf("decode: %v", err)
}
fmt.Println(string(dst[:n]))
// {"host":"db-prod","port":5432}
}DecodedLen retorna un límite superior, no la longitud exacta. Usa siempre el valor de retorno n de Decode para recortar el resultado correctamente: dst[:n].StdEncoding vs URLEncoding — Elegir la variante correcta
Aquí es donde se origina la mayor parte de la confusión. encoding/base64 de Go expone cuatro objetos de codificación, y elegir el incorrecto te dará un error garantizado. La diferencia se reduce a dos aspectos: el alfabeto y el padding.
package main
import (
"encoding/base64"
"fmt"
)
func main() {
// JWT header payload — URL-safe, no padding
jwtHeader := "eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjMtMDkifQ"
// Wrong: StdEncoding fails on URL-safe input without padding
_, err1 := base64.StdEncoding.DecodeString(jwtHeader)
fmt.Println("StdEncoding error:", err1)
// StdEncoding error: illegal base64 data at input byte 43
// Correct: RawURLEncoding — no padding, URL-safe alphabet
decoded, err2 := base64.RawURLEncoding.DecodeString(jwtHeader)
fmt.Println("RawURLEncoding ok:", err2, "→", string(decoded))
// RawURLEncoding ok: <nil> → {"alg":"RS256","kid":"2023-09"}
}Las cuatro variantes en términos sencillos:
Mi regla general: si el dato viene de un JWT, un flujo OAuth o un SDK de proveedor cloud, prueba primero con RawURLEncoding. Si viene de adjuntos de email o formularios web clásicos, prueba con StdEncoding. El mensaje de error siempre indica la posición exacta del byte donde falló la decodificación.
Decodificar Base64 desde un archivo y una respuesta de API
Leer un archivo codificado en Base64
Los archivos binarios (imágenes, PDFs, certificados) a veces se almacenan en disco codificados en Base64. Lee el archivo, elimina los espacios en blanco del final y luego decodifica:
package main
import (
"encoding/base64"
"fmt"
"log"
"os"
"strings"
)
func main() {
raw, err := os.ReadFile("certificate.pem.b64")
if err != nil {
log.Fatalf("read file: %v", err)
}
// Strip newlines — Base64 files often have line breaks every 76 chars
cleaned := strings.ReplaceAll(strings.TrimSpace(string(raw)), "\n", "")
decoded, err := base64.StdEncoding.DecodeString(cleaned)
if err != nil {
log.Fatalf("decode: %v", err)
}
if err := os.WriteFile("certificate.pem", decoded, 0600); err != nil {
log.Fatalf("write: %v", err)
}
fmt.Printf("decoded %d bytes → certificate.pem\n", len(decoded))
}Decodificar un campo Base64 de una respuesta JSON de API
Las APIs cloud frecuentemente retornan datos binarios (claves de cifrado, blobs firmados, miniaturas) como strings Base64 dentro de JSON. Primero deserializa el JSON y luego decodifica el campo correspondiente:
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
)
type SecretResponse struct {
Name string `json:"name"`
Payload string `json:"payload"` // Base64-encoded secret value
Version int `json:"version"`
}
func fetchAndDecodeSecret(secretURL string) ([]byte, error) {
resp, err := http.Get(secretURL)
if err != nil {
return nil, fmt.Errorf("http get: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
var secret SecretResponse
if err := json.NewDecoder(resp.Body).Decode(&secret); err != nil {
return nil, fmt.Errorf("decode json: %w", err)
}
value, err := base64.StdEncoding.DecodeString(secret.Payload)
if err != nil {
return nil, fmt.Errorf("decode base64: %w", err)
}
return value, nil
}
func main() {
value, err := fetchAndDecodeSecret("https://api.example.com/secrets/db-password")
if err != nil {
log.Fatalf("fetch secret: %v", err)
}
fmt.Printf("secret value: %s\n", value)
}fmt.Errorf("decode base64: %w", err) en lugar de perder el contexto. El mensaje de error original de encoding/base64 incluye la posición del byte donde ocurrió el fallo, lo cual es útil durante la depuración.Decodificación en streaming de archivos Base64 de gran tamaño
Cargar en memoria un archivo codificado en Base64 de 500 MB con os.ReadFile y luego llamar a DecodeString consume aproximadamente 750 MB de RAM (el string codificado más los bytes decodificados). base64.NewDecoder envuelve cualquier io.Reader y decodifica en fragmentos, manteniendo el uso de memoria casi constante. Combínalo con io.Copy para un pipeline de streaming limpio:
package main
import (
"encoding/base64"
"fmt"
"io"
"log"
"os"
)
func streamDecodeFile(srcPath, dstPath string) error {
src, err := os.Open(srcPath)
if err != nil {
return fmt.Errorf("open source: %w", err)
}
defer src.Close()
dst, err := os.Create(dstPath)
if err != nil {
return fmt.Errorf("create dest: %w", err)
}
defer dst.Close()
decoder := base64.NewDecoder(base64.StdEncoding, src)
written, err := io.Copy(dst, decoder)
if err != nil {
return fmt.Errorf("stream decode: %w", err)
}
fmt.Printf("written %d bytes to %s\n", written, dstPath)
return nil
}
func main() {
if err := streamDecodeFile("backup.tar.b64", "backup.tar"); err != nil {
log.Fatal(err)
}
}base64.NewDecoder espera datos Base64 limpios y sin interrupciones. Si el archivo fuente tiene saltos de línea (habitual en archivos PEM y MIME), envuelve el reader fuente con un reader que elimine las líneas, o preprocesa el archivo para quitar los saltos de línea antes de hacer el streaming.Decodificación Base64 desde la línea de comandos
La mayoría de los desarrolladores Go recurren primero a la línea de comandos cuando depuran. Todos los sistemas macOS y Linux incluyen base64; en Windows, PowerShell tiene un equivalente integrado. Para inspección rápida de payloads de API, estas opciones son más rápidas que escribir un script en Go.
# Decode a Base64 string (Linux / macOS)
echo "eyJob3N0IjoiZGItcHJvZCIsInBvcnQiOjU0MzJ9" | base64 --decode
# {"host":"db-prod","port":5432}
# Decode and pretty-print with jq (pipe the JSON output)
echo "eyJob3N0IjoiZGItcHJvZCIsInBvcnQiOjU0MzJ9" | base64 --decode | jq .
# {
# "host": "db-prod",
# "port": 5432
# }
# Decode a Base64-encoded file to binary
base64 --decode < encrypted_payload.b64 > encrypted_payload.bin
# macOS uses -D flag instead of --decode
echo "c2VjcmV0LXRva2Vu" | base64 -DPara inspeccionar tokens JWT sin ninguna herramienta instalada, pega el token en el decodificador Base64 de ToolDeck — divide por los puntos y decodifica cada parte.
Alternativa de alto rendimiento: encoding/base64 ya es rápido
A diferencia de Python, donde orjson vs json es una conversación de rendimiento con sentido, encoding/base64 de Go ya está optimizado en ensamblador y es genuinamente rápido para la mayoría de las cargas de trabajo. Dicho esto, si procesas millones de registros en un bucle ajustado, filippo.io/base64 ofrece decodificación acelerada con SIMD y una API de reemplazo directo.
go get filippo.io/base64
package main
import (
"fmt"
"log"
"filippo.io/base64"
)
func main() {
// Drop-in replacement — same API as encoding/base64
encoded := "eyJob3N0IjoiY2FjaGUtcHJvZCIsInBvcnQiOjYzNzl9"
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
log.Fatalf("decode: %v", err)
}
fmt.Println(string(decoded))
// {"host":"cache-prod","port":6379}
}La mejora de rendimiento es más notable en amd64 con soporte AVX2 — los benchmarks muestran una mejora de throughput de 2–4x en entradas grandes. Para la decodificación habitual de respuestas de API (unos pocos cientos de bytes a la vez), quédate con la librería estándar.
Decodificar el payload JWT Base64 en Go
La inspección de JWT aparece en casi todos los servicios backend. En mi experiencia, la mayoría de las sesiones de depuración se reducen a “¿qué hay realmente en este token?” — y puedes responderlo sin necesidad de incorporar una librería JWT completa. El token tiene tres segmentos codificados en Base64url separados por puntos. El segmento central es el payload que realmente te importa:
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
"strings"
)
type JWTPayload struct {
Subject string `json:"sub"`
Issuer string `json:"iss"`
Expiry int64 `json:"exp"`
Roles []string `json:"roles"`
}
func decodeJWTPayload(token string) (*JWTPayload, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid JWT: expected 3 segments, got %d", len(parts))
}
// JWT uses RawURLEncoding — URL-safe alphabet, no = padding
raw, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("decode payload: %w", err)
}
var payload JWTPayload
if err := json.Unmarshal(raw, &payload); err != nil {
return nil, fmt.Errorf("unmarshal payload: %w", err)
}
return &payload, nil
}
func main() {
token := "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3ItNDQyIiwiaXNzIjoiYXV0aC5leGFtcGxlLmNvbSIsImV4cCI6MTc0MTk1NjgwMCwicm9sZXMiOlsiYWRtaW4iLCJhdWRpdG9yIl19.SIGNATURE"
payload, err := decodeJWTPayload(token)
if err != nil {
log.Fatalf("jwt: %v", err)
}
fmt.Printf("Subject: %s\n", payload.Subject)
fmt.Printf("Issuer: %s\n", payload.Issuer)
fmt.Printf("Roles: %v\n", payload.Roles)
// Subject: usr-442
// Issuer: auth.example.com
// Roles: [admin auditor]
}Errores comunes
He encontrado los cuatro en revisiones de código reales — los dos primeros aparecen casi siempre que alguien integra un nuevo proveedor de autenticación.
Problema: Los tokens JWT y OAuth usan el alfabeto Base64 URL-safe (- y _). Pasarlos a StdEncoding.DecodeString falla con 'illegal base64 data'.
Solución: Verifica el origen de la entrada: los tokens de sistemas de autenticación usan RawURLEncoding; los adjuntos binarios usan StdEncoding.
// JWT header — URL-safe, no padding token := "eyJhbGciOiJSUzI1NiJ9" decoded, err := base64.StdEncoding.DecodeString(token) // err: illegal base64 data at input byte 19
// JWT header — correct encoding
token := "eyJhbGciOiJSUzI1NiJ9"
decoded, err := base64.RawURLEncoding.DecodeString(token)
// decoded: {"alg":"RS256"}
// err: nilProblema: Decode escribe en un buffer preasignado y retorna el número de bytes realmente escritos. DecodedLen retorna un límite superior, por lo que la cola del buffer puede contener bytes basura.
Solución: Recorta siempre el resultado con dst[:n] — no con dst[:len(dst)].
dst := make([]byte, base64.StdEncoding.DecodedLen(len(src))) base64.StdEncoding.Decode(dst, src) fmt.Println(string(dst)) // may include trailing zero bytes
dst := make([]byte, base64.StdEncoding.DecodedLen(len(src)))
n, err := base64.StdEncoding.Decode(dst, src)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(dst[:n])) // correct — only the decoded bytesProblema: Los strings Base64 copiados desde terminales, emails o archivos de configuración suelen tener saltos de línea o espacios al final. Pasarlos directamente a DecodeString falla en el carácter de espacio en blanco.
Solución: Llama a strings.TrimSpace (y strings.ReplaceAll para saltos de línea embebidos) antes de decodificar.
// Value read from a config file with a trailing newline encoded := "c2VydmljZV9rZXk6eEtNcDI=\n" decoded, err := base64.StdEncoding.DecodeString(encoded) // err: illegal base64 data at input byte 24
encoded := "c2VydmljZV9rZXk6eEtNcDI=\n" cleaned := strings.TrimSpace(encoded) decoded, err := base64.StdEncoding.DecodeString(cleaned) // decoded: "service_key:xKMp2" // err: nil
Problema: Llamar a string(decoded) sobre datos binarios (imágenes, payloads comprimidos) produce strings UTF-8 inválidos. Go puede almacenar bytes arbitrarios en strings, pero algunas operaciones pueden corromper el contenido.
Solución: Mantén los datos binarios como []byte a lo largo de todo el pipeline. Llama a string(decoded) solo cuando el contenido decodificado sea texto garantizado.
decoded, _ := base64.StdEncoding.DecodeString(pngBase64)
// Treating binary PNG as a string loses data
imageStr := string(decoded)
os.WriteFile("image.png", []byte(imageStr), 0644) // may corruptdecoded, err := base64.StdEncoding.DecodeString(pngBase64)
if err != nil {
log.Fatal(err)
}
// Write bytes directly — no string conversion
os.WriteFile("image.png", decoded, 0644)Comparación de métodos
Todas las variantes se incluyen en la librería estándar — ninguna de estas opciones requiere dependencias externas.
Para tokens JWT y flujos OAuth: RawURLEncoding. Para adjuntos de email y datos MIME: StdEncoding. Para archivos binarios grandes de disco o red: envuelve un reader con base64.NewDecoder — mantiene el uso de memoria constante independientemente del tamaño del archivo. ¿Necesitas un alfabeto personalizado? base64.NewEncoding(alphabet) construye un nuevo objeto de codificación para casos de uso especiales.
Para comprobaciones puntuales durante el desarrollo, el decodificador Base64 online es más rápido que arrancar un programa en Go.
Preguntas frecuentes
¿Cómo decodifico un string Base64 en Go?
Importa encoding/base64 y llama a base64.StdEncoding.DecodeString(s). Retorna ([]byte, error) — siempre verifica el error. Si el string usa caracteres URL-safe (- y _ en lugar de + y /), usa base64.URLEncoding.DecodeString. Para tokens JWT y la mayoría de las APIs modernas, RawURLEncoding (sin padding) es la opción correcta.
package main
import (
"encoding/base64"
"fmt"
"log"
)
func main() {
encoded := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
decoded, err := base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
log.Fatalf("decode: %v", err)
}
fmt.Println(string(decoded))
// {"alg":"HS256","typ":"JWT"}
}¿Cuál es la diferencia entre StdEncoding y URLEncoding en Go?
StdEncoding usa el alfabeto Base64 estándar con los caracteres + y / y padding = — definido en RFC 4648 §4. URLEncoding sustituye + por - y / por _, haciendo que la salida sea segura en URLs y cabeceras HTTP sin necesidad de percent-encoding — definido en RFC 4648 §5. Usa URLEncoding para tokens JWT, tokens OAuth y cualquier dato embebido en query strings.
package main
import (
"encoding/base64"
"fmt"
)
func main() {
// Standard: may contain + / and = characters
std := base64.StdEncoding.EncodeToString([]byte("hello/world"))
fmt.Println(std) // "aGVsbG8vd29ybGQ="
// URL-safe: replaces + with - and / with _
url := base64.URLEncoding.EncodeToString([]byte("hello/world"))
fmt.Println(url) // "aGVsbG8vd29ybGQ=" (same — diff shows with different bytes)
// JWT headers never have padding — use RawURLEncoding
raw := base64.RawURLEncoding.EncodeToString([]byte("hello/world"))
fmt.Println(raw) // "aGVsbG8vd29ybGQ" (no trailing =)
}¿Cómo corrijo errores de "illegal base64 data" en Go?
Este error significa que la entrada contiene caracteres fuera del alfabeto esperado o que el padding es incorrecto. Tres causas comunes: usar StdEncoding con entrada URL-safe (cambia a URLEncoding), usar un encoder con padding sobre entrada sin padding (cambia a RawStdEncoding/RawURLEncoding), o espacios/saltos de línea al final. Elimina los espacios con strings.TrimSpace antes de decodificar.
package main
import (
"encoding/base64"
"fmt"
"log"
"strings"
)
func main() {
// Input from a webhook payload — has newlines stripped from wire format
raw := " aGVsbG8gd29ybGQ= \n"
cleaned := strings.TrimSpace(raw)
decoded, err := base64.StdEncoding.DecodeString(cleaned)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(decoded)) // hello world
}¿Cómo decodifico en streaming un archivo Base64 de gran tamaño en Go?
Usa base64.NewDecoder(base64.StdEncoding, reader), que envuelve cualquier io.Reader y decodifica al vuelo. Conecta la salida a io.Copy para escribir en el destino sin cargar el archivo completo en memoria. Es el patrón estándar para decodificar adjuntos binarios codificados en Base64 o payloads de datos de gran tamaño.
package main
import (
"encoding/base64"
"io"
"log"
"os"
)
func main() {
src, err := os.Open("attachment.b64")
if err != nil {
log.Fatal(err)
}
defer src.Close()
dst, err := os.Create("attachment.bin")
if err != nil {
log.Fatal(err)
}
defer dst.Close()
decoder := base64.NewDecoder(base64.StdEncoding, src)
io.Copy(dst, decoder)
}¿Puedo decodificar el payload de un JWT Base64 en Go sin una librería JWT?
Sí. Un JWT tiene tres segmentos codificados en Base64url separados por puntos. Divide el string por "." y decodifica el segundo segmento (índice 1) con base64.RawURLEncoding.DecodeString — las cabeceras y payloads de JWT usan el alfabeto URL-safe sin padding. El segmento de firma (índice 2) es binario y normalmente solo se necesita para verificación.
package main
import (
"encoding/base64"
"fmt"
"log"
"strings"
)
func main() {
token := "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3ItOTAxIiwicm9sZSI6ImFkbWluIn0.SIG"
parts := strings.Split(token, ".")
if len(parts) < 2 {
log.Fatal("invalid JWT format")
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
log.Fatalf("decode payload: %v", err)
}
fmt.Println(string(payload))
// {"sub":"usr-901","role":"admin"}
}¿Qué codificación debo usar para decodificar datos Base64 de una respuesta de API HTTP?
Revisa la documentación de la API o inspecciona el string codificado. Si contiene los caracteres + o / y termina con =, usa StdEncoding. Si usa los caracteres - y _ sin =, usa RawURLEncoding. Si no estás seguro, prueba primero con RawURLEncoding — la mayoría de las APIs modernas (OAuth2, JWT, Google Cloud, AWS) usan Base64 URL-safe sin padding.
package main
import (
"encoding/base64"
"strings"
)
// Detect encoding variant from the encoded string
func decodeAPIPayload(encoded string) ([]byte, error) {
// URL-safe characters without padding — common in modern APIs
if !strings.Contains(encoded, "+") && !strings.Contains(encoded, "/") {
return base64.RawURLEncoding.DecodeString(encoded)
}
// Standard Base64 with padding
return base64.StdEncoding.DecodeString(encoded)
}Herramientas relacionadas
- Codificador Base64 — codifica datos binarios o texto a Base64 en el navegador, útil para generar fixtures de prueba que puedas pegar directamente en tus tests unitarios de Go.
- Decodificador JWT — divide y decodifica los tres segmentos de un JWT a la vez, con inspección campo a campo del payload — sin necesidad de código Go cuando solo quieres leer un token durante la depuración.
- Decodificador URL — decodifica strings con percent-encoding, útil cuando las respuestas de API mezclan datos Base64url con parámetros de query percent-encoded.
- Formateador JSON — tras decodificar un payload JWT Base64 o una respuesta de API, pega el JSON aquí para visualizarlo con formato y validar la estructura al instante.