The Four Encoding Variants in encoding/base64
Go's encoding/base64 package ships four pre-defined*Encoding values. Picking the right one is 90% of debugging decode errors:
base64.StdEncoding— RFC 4648 §4 alphabet (+,/), with=padding. For HTTP Basic Auth, MIME, data URLs, generic encoded binary.base64.URLEncoding— RFC 4648 §5 URL-safe alphabet (-,_), with=padding. For URL query parameters that need padding.base64.RawStdEncoding— Standard alphabet, no padding. For code-golfed payloads where=is undesirable.base64.RawURLEncoding— URL-safe alphabet, no padding. For JWT headers, JWT payloads, and JWS/JWE segments.
All four implement the same methods: EncodeToString, DecodeString,Encode, Decode, EncodedLen, DecodedLen. They differ only in alphabet and padding behaviour.
Decoding a Standard Base64 String
package main
import (
"encoding/base64"
"fmt"
"log"
)
func main() {
encoded := "SGVsbG8g5LiW55WMIPCfkYs="
// Decode to raw bytes
data, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
log.Fatalf("decode: %v", err)
}
// Interpret bytes as a UTF-8 string
text := string(data)
fmt.Println(text)
// Hello 世界 👋
// Length check — DecodedLen is a maximum, actual len(data) may be smaller
fmt.Println("decoded bytes:", len(data))
fmt.Println("max possible:", base64.StdEncoding.DecodedLen(len(encoded)))
}Decoding Without Allocating a String
package main
import (
"encoding/base64"
"fmt"
)
// Decode into a pre-allocated buffer — zero garbage if you reuse the buffer
func decodeInto(dst, src []byte) (n int, err error) {
return base64.StdEncoding.Decode(dst, src)
}
func main() {
encoded := []byte("SGVsbG8=")
dst := make([]byte, base64.StdEncoding.DecodedLen(len(encoded)))
n, err := decodeInto(dst, encoded)
if err != nil {
panic(err)
}
fmt.Println(string(dst[:n])) // Hello
}Decoding a JWT Segment
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
)
type JWTHeader struct {
Alg string "json:\"alg\""
Typ string "json:\"typ\""
Kid string "json:\"kid,omitempty\""
}
func parseJWTHeader(token string) (*JWTHeader, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("expected 3 segments, got %d", len(parts))
}
// JWT uses RawURLEncoding — URL-safe alphabet, NO padding.
// StdEncoding here would throw CorruptInputError.
headerBytes, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
return nil, fmt.Errorf("decode header: %w", err)
}
var h JWTHeader
if err := json.Unmarshal(headerBytes, &h); err != nil {
return nil, fmt.Errorf("unmarshal header: %w", err)
}
return &h, nil
}
func main() {
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
"eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJhbGljZSJ9." +
"SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
header, err := parseJWTHeader(token)
if err != nil {
panic(err)
}
fmt.Printf("alg=%s typ=%s\n", header.Alg, header.Typ)
// alg=HS256 typ=JWT
}Decoding With Full Error Context
package main
import (
"encoding/base64"
"errors"
"fmt"
)
func decodeWithContext(s string) ([]byte, error) {
data, err := base64.StdEncoding.DecodeString(s)
if err == nil {
return data, nil
}
// Try to extract the exact byte offset of the bad character
var cerr base64.CorruptInputError
if errors.As(err, &cerr) {
offset := int64(cerr)
start := int64(0)
if offset > 5 {
start = offset - 5
}
end := offset + 5
if end > int64(len(s)) {
end = int64(len(s))
}
return nil, fmt.Errorf(
"invalid base64 at offset %d, context %q",
offset,
s[start:end],
)
}
return nil, fmt.Errorf("decode: %w", err)
}
func main() {
_, err := decodeWithContext("Hello!!!InvalidBase64===")
fmt.Println(err)
// invalid base64 at offset 5, context "Hello"
}Streaming Decode for Large Inputs
package main
import (
"encoding/base64"
"io"
"log"
"os"
)
// Decode a Base64 file to a binary file without loading either in memory
func decodeFile(inPath, outPath string) error {
in, err := os.Open(inPath)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(outPath)
if err != nil {
return err
}
defer out.Close()
decoder := base64.NewDecoder(base64.StdEncoding, in)
_, err = io.Copy(out, decoder)
return err
}
func main() {
if err := decodeFile("input.b64", "output.bin"); err != nil {
log.Fatal(err)
}
}
// Note: base64.NewDecoder tolerates CR/LF whitespace — handy for MIME
// multipart bodies with line-wrapped Base64 (76 chars per line).Decoding a Data URL
package main
import (
"encoding/base64"
"fmt"
"mime"
"strings"
)
func decodeDataURL(dataURL string) (mediaType string, data []byte, err error) {
if !strings.HasPrefix(dataURL, "data:") {
return "", nil, fmt.Errorf("not a data URL")
}
rest := dataURL[5:] // strip "data:"
comma := strings.IndexByte(rest, ',')
if comma < 0 {
return "", nil, fmt.Errorf("missing comma in data URL")
}
metaPart := rest[:comma]
body := rest[comma+1:]
isBase64 := strings.HasSuffix(metaPart, ";base64")
if isBase64 {
metaPart = strings.TrimSuffix(metaPart, ";base64")
}
if metaPart == "" {
metaPart = "text/plain;charset=US-ASCII"
}
mt, _, _ := mime.ParseMediaType(metaPart)
if isBase64 {
decoded, err := base64.StdEncoding.DecodeString(body)
if err != nil {
return mt, nil, err
}
return mt, decoded, nil
}
return mt, []byte(body), nil
}
func main() {
url := "data:text/plain;base64,SGVsbG8="
mt, data, err := decodeDataURL(url)
if err != nil {
panic(err)
}
fmt.Printf("%s -> %q\n", mt, data)
// text/plain -> "Hello"
}Decoding HTTP Basic Auth
package main
import (
"encoding/base64"
"fmt"
"net/http"
"strings"
)
func parseBasicAuth(header string) (user, pass string, ok bool) {
const prefix = "Basic "
if !strings.HasPrefix(header, prefix) {
return "", "", false
}
decoded, err := base64.StdEncoding.DecodeString(header[len(prefix):])
if err != nil {
return "", "", false
}
s := string(decoded)
i := strings.IndexByte(s, ':')
if i < 0 {
return "", "", false
}
return s[:i], s[i+1:], true
}
func handler(w http.ResponseWriter, r *http.Request) {
user, pass, ok := parseBasicAuth(r.Header.Get("Authorization"))
if !ok {
w.Header().Set("WWW-Authenticate", "Basic realm=\"api\"")
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "user=%s pass=%s", user, pass)
}
// Standard library has r.BasicAuth() but implementing it manually here
// so you can see the exact decode path.Common Pitfalls in Go Base64 Decode Code
- Using StdEncoding on a JWT segment — JWTs use URL-safe alphabet and no padding.
base64.StdEncoding.DecodeString(jwtPart)returnsbase64.CorruptInputErroron the first-or_character. Usebase64.RawURLEncoding. - Ignoring the error from DecodeString — Go idiom is to always check
err. If you discard it, invalid input silently returns an empty byte slice and your program marches on with wrong data. - Confusing DecodedLen with actual length —
DecodedLen(n)returns the maximum possible decoded length for an input of lengthn. The actual byte count can be 1-2 bytes shorter depending on padding. Always uselen(data)after the decode call, or thenreturn value fromDecode(dst, src). - Reading Base64 with CR/LF through DecodeString —
DecodeStringis strict about whitespace. MIME-formatted Base64 (76 chars per line with CR/LF) needsbase64.NewDecoderwhich tolerates line breaks, or you must strip whitespace first. - Expecting io.EOF to be nil at end of stream — When using
base64.NewDecoderwithio.Copy, EOF is handled internally and does not propagate. But if you read manually withdecoder.Read(), you must handleio.EOFlike any other reader.
Key Facts
- Package:
- encoding/base64 — standard library, no module install
- Standard decode:
- base64.StdEncoding.DecodeString(s)
- URL-safe decode:
- base64.URLEncoding.DecodeString(s)
- JWT decode:
- base64.RawURLEncoding.DecodeString(s)
- Streaming:
- base64.NewDecoder(encoding, reader) — tolerates whitespace
- No-alloc decode:
- Decode(dst, src) into pre-sized buffer
- Error type:
- base64.CorruptInputError (int64 offset of first bad byte)
- Max output size:
- DecodedLen(n) — upper bound, use len(result) for exact
Related Base64 Tools
- Base64 Encode in Go — the encoding counterpart
- Base64 Decode Online — browser decoder
- Base64 Decode in Python — Python 3 equivalent
- Base64 Decode in JavaScript — Node and browser
- URL-Safe Base64 — cross-language URL encoding
- JWT Debugger — full JWT inspector
- JWT Decoder in Go — full JWT parse in Go