Codificar y decodificar una cadena
Las cadenas de Go ya son UTF-8, así que basta con convertirlas a []byte:
package main
import (
"encoding/base64"
"fmt"
)
func main() {
enc := base64.StdEncoding.EncodeToString([]byte("héllo wörld"))
fmt.Println(enc) // aMOpbGxvIHfDtnJsZA==
dec, err := base64.StdEncoding.DecodeString(enc)
if err != nil {
panic(err)
}
fmt.Println(string(dec)) // héllo wörld
}
Comprueba siempre err. Cuando falla, DecodeString puede devolver los bytes decodificados hasta ese punto junto con el error, así que usar el resultado sin comprobarlo te da datos truncados sin avisar.
Las cuatro codificaciones: Std, URL, Raw
| Variable | Alfabeto | Relleno | <<???>> se codifica como |
|---|---|---|---|
StdEncoding | + / | sí | PDw/Pz8+Pg== |
URLEncoding | - _ | sí | PDw_Pz8-Pg== |
RawStdEncoding | + / | no | PDw/Pz8+Pg |
RawURLEncoding | - _ | no | PDw_Pz8-Pg |
d := []byte("<<???>>")
base64.URLEncoding.EncodeToString(d) // PDw_Pz8-Pg==
base64.RawURLEncoding.EncodeToString(d) // PDw_Pz8-Pg
b, err := base64.RawURLEncoding.DecodeString("PDw_Pz8-Pg") // "<<???>>", nil
La decodificación es estricta con el relleno en ambos sentidos. Las codificaciones con relleno rechazan las entradas sin él, y las Raw rechazan el =:
base64.StdEncoding.DecodeString("aGk")
// illegal base64 data at input byte 0
base64.RawURLEncoding.DecodeString("PDw_Pz8-Pg==")
// illegal base64 data at input byte 10
Si aceptas entradas de varias fuentes, quita los = finales con strings.TrimRight(s, "=") y decodifica con una codificación Raw. También puedes crear codificaciones propias con base64.NewEncoding(alphabet) y .WithPadding(base64.NoPadding). La página Base64 frente a Base64URL explica dónde aparece cada variante.
Codificar y decodificar un archivo
data, err := os.ReadFile("hello.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(data)) // aGVsbG8K
raw, err := base64.StdEncoding.DecodeString("aGVsbG8K")
if err != nil {
log.Fatal(err)
}
os.WriteFile("copy.txt", raw, 0o644)
Flujos con NewEncoder y NewDecoder
Con archivos grandes, envuelve un io.Writer o un io.Reader. El uso de memoria se mantiene constante:
in, _ := os.Open("big.bin")
defer in.Close()
out, _ := os.Create("big.b64")
defer out.Close()
w := base64.NewEncoder(base64.StdEncoding, out)
if _, err := io.Copy(w, in); err != nil {
log.Fatal(err)
}
if err := w.Close(); err != nil { // flushes the last partial block
log.Fatal(err)
}
w.Close() no cierra out: escribe los últimos uno o dos bytes y el relleno. Olvidarlo es el error clásico de Base64 en Go: la salida parece correcta, pero le faltan hasta cuatro caracteres. La decodificación es simétrica:
r := base64.NewDecoder(base64.StdEncoding, f)
io.Copy(dst, r)
Con un archivo de prueba de 300 KB, los resultados en flujo y en memoria fueron idénticos. El decodificador se salta \r y \n, así que lee directamente entradas con cortes MIME. Para calcular de antemano el tamaño de los búferes, EncodedLen(5) devuelve 8 y RawStdEncoding.EncodedLen(5) devuelve 7; la guía del aumento de tamaño tiene la fórmula que hay detrás.
Go 1.22 añadió AppendEncode y AppendDecode, que añaden el resultado a un slice existente. Resultan prácticos para cabeceras:
dst := base64.StdEncoding.AppendEncode([]byte("Basic "), []byte("user:pass"))
fmt.Println(string(dst)) // Basic dXNlcjpwYXNz
El generador de cabeceras Basic Auth construye el mismo valor en el navegador.
Validación y CorruptInputError
Todos los errores de decodificación son base64.CorruptInputError, un int64 con la posición del primer byte incorrecto:
_, err := base64.StdEncoding.DecodeString("PDw_Pz8-Pg==")
var ce base64.CorruptInputError
if errors.As(err, &ce) {
fmt.Println(err) // illegal base64 data at input byte 3
}
Resultados que obtuvimos con StdEncoding:
| Entrada | Error |
|---|---|
PDw_Pz8-Pg== | illegal base64 data at input byte 3 (el _) |
aGk | illegal base64 data at input byte 0 |
aGk=!! | illegal base64 data at input byte 4 |
aGk== | illegal base64 data at input byte 4 |
aGVs bG8K | illegal base64 data at input byte 4 (espacio) |
aGl= | sin error, devuelve hi |
"aGVs\nbG8K" | sin error, se salta el salto de línea |
aGl= tiene bits de relleno distintos de cero. Para rechazar entradas no canónicas como esa, usa .Strict():
_, err = base64.StdEncoding.Strict().DecodeString("aGl=")
// illegal base64 data at input byte 3
El validador Base64 marca las mismas posiciones; en errores habituales de Base64 están las causas más frecuentes.
Trampas
- Decodificar las partes de un JWT con
StdEncoding. Los tokens son Base64URL sin relleno. UsaRawURLEncoding, o inspecciona antes el token con el decodificador JWT. - Pasar por alto el resultado parcial.
"aGk=!!"devuelve"hi"y un error. No uses los bytes cuandoerr != nil. - Olvidar
Close()en el codificador. Se pierde el último bloque. - Espacios al copiar y pegar. El decodificador solo se salta
\ry\n. Elimina antes el resto de espacios en blanco constrings.Join(strings.Fields(s), "").
Notas de versión
RawStdEncodingyRawURLEncoding: Go 1.5.Strict(): Go 1.8.AppendEncode,AppendDecode: Go 1.22.- El paquete no tiene un codificador con cortes de línea MIME; los paquetes
mimeynet/mailse ocupan del correo, o puedes insertar tú mismo\r\ncada 76 caracteres.
Preguntas frecuentes
¿Qué diferencia hay entre URLEncoding y RawURLEncoding?
Las dos usan el alfabeto URL-safe con - y _. URLEncoding escribe el relleno = y lo exige al decodificar; RawURLEncoding lo omite y lo rechaza. Los JWT usan RawURLEncoding.
¿Qué significa «illegal base64 data at input byte 0»?
Es un base64.CorruptInputError, y el número es la posición en la que falló la decodificación. En el byte 0 y con una cadena corta suele indicar una longitud incorrecta, a menudo por falta de relleno; usa la codificación Raw para entradas sin relleno.
¿El decodificador Base64 de Go ignora los saltos de línea?
Sí. Las cuatro codificaciones se saltan \r y \n, así que las entradas con cortes MIME se decodifican. Los espacios y otros caracteres son errores.