GoでのBase64デコードはJWTの検査、バイナリファイルの添付、クラウドサービスからのAPIペイロードなど、 常に必要とされる処理です。Goの標準ライブラリ encoding/base64パッケージはこれらすべてに 対応していますが、エンコーディングバリアントの選択ミス (標準 vs URLセーフ、パディングあり vs なし) が “illegal base64 data” エラーの最も多い原因です。私自身、GoサービスにJWT認証を初めて組み込んだ際、StdEncodingとRawURLEncodingの選択ミスで丸一日デバッグしました。このガイドでは StdEncoding、URLEncoding、RawURLEncoding、base64.NewDecoder を使った ストリーミングデコード、JWTペイロードの検査、そして誰もが最初に引っかかる4つのミスを解説します。 ブラウザで手軽にデコードするには、 ToolDeckのBase64 Decoder を使えば一行もコードを書かずに即座に処理できます。
- ✓encoding/base64 はGoの標準ライブラリに含まれており、go get は不要
- ✓JWTトークンや最近のAPIには RawURLEncoding を使用する (パディングなし、URLセーフアルファベット)
- ✓StdEncoding は + と / に = パディングを使用し、URLEncoding は - と _ に置き換えるがパディングは保持
- ✓base64.NewDecoder は任意の io.Reader をラップし、メモリに読み込まずにストリームデコードが可能
- ✓エラーは必ず確認すること — パディング不正やアルファベット不一致は illegal base64 data を引き起こす
Base64デコードとは?
Base64エンコーディングはバイナリデータを64種類の印刷可能文字 (A〜Z、a〜z、0〜9、および2つの追加文字) を使ったASCIIテキストとして表現します。デコードはその逆で、ASCII表現を元のバイト列に戻します。 Base64の4文字は正確に3バイトにデコードされます。このスキームはメール、HTTPヘッダー、JSONフィールドなど 多くの転送層がテキスト向けに設計されており、生のバイナリデータを扱えないために存在します。 ラウンドトリップの例を以下に示します。
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
}encoding/base64 でGoのBase64デコード
encoding/base64 パッケージはGoに同梱されており、外部依存は不要です。 パッケージレベルの変数として4つの定義済みエンコーディングバリアントを公開しています。 文字列入力に最もよく使われる関数は DecodeString で、バイトスライスとエラーを返します。
package main
import (
"encoding/base64"
"fmt"
"log"
)
func main() {
// Standard Base64 — the alphabet uses + and / with = padding
encoded := "eyJob3N0IjoiZGItcHJvZCF1cy1lYXN0LTEiLCJwb3J0Ijo1NDMyfQ=="
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}
}Decode メソッドは文字列ではなくバイトスライスを入力として受け取り、事前に確保した 宛先バッファに結果を書き込みます。バッファのサイズを正しく設定する必要があります。 base64.StdEncoding.DecodedLen(len(src)) を使って最大サイズを取得できます (パディングの影響で実際のデコード長より数バイト大きくなる場合があります)。
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 は正確な長さではなく上限値を返します。結果を正しくスライスするには、Decode の戻り値 n を必ず使用してください: dst[:n]。StdEncoding vs URLEncoding — 適切なバリアントの選択
ここが最も混乱しやすい箇所です。Goの encoding/base64 は4つのエンコーディングオブジェクトを 公開しており、誤ったものを選ぶと必ずエラーになります。違いはアルファベットとパディングの2点です。
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"}
}4つのバリアントをわかりやすく説明します。
経験則として、JWTやOAuthフロー、クラウドプロバイダーSDKからのデータには RawURLEncoding を まず試してください。メール添付ファイルや旧来のWebフォームからのデータには StdEncoding を 試してください。エラーメッセージには常にデコードが失敗した正確なバイト位置が含まれます。
ファイルとAPIレスポンスからのBase64デコード
Base64エンコードされたファイルの読み込み
バイナリファイル (画像、PDF、証明書) がBase64エンコードされてディスクに保存されている場合があります。 ファイルを読み込み、末尾の空白を除去してからデコードします。
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))
}APIのJSONレスポンスからBase64フィールドをデコードする
クラウドAPIはバイナリデータ (暗号化キー、署名済みBLOB、サムネイルなど) をJSON内のBase64文字列として 返すことがよくあります。まずJSONをアンマーシャルし、対象フィールドをデコードします。
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) でラップして コンテキストを失わないようにしてください。encoding/base64 のオリジナルエラーメッセージには 失敗したバイト位置が含まれており、デバッグ時に役立ちます。大容量のBase64エンコードファイルのストリーミング
500 MBのBase64エンコードファイルを os.ReadFile でメモリに読み込んでから DecodeString を呼び出すと、約750 MBのRAMを消費します (エンコード文字列とデコードバイトの合計)。base64.NewDecoder は任意の io.Reader をラップしてチャンク単位でデコードするため、 メモリ使用量をほぼ一定に保ちます。io.Copy と組み合わせてクリーンなストリーミングパイプラインを 構築できます。
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 は中断のないクリーンなBase64データを期待します。 ソースファイルに改行が含まれている場合 (PEMやMIMEエンコードファイルでは一般的)、ソースリーダーを 改行除去リーダーでラップするか、ストリーミング前にファイルから改行を削除する前処理を行ってください。コマンドラインでのBase64デコード
Goの開発者はデバッグ時にまずコマンドラインを使うことが多いでしょう。macOSとLinuxのシステムにはbase64 コマンドが同梱されており、WindowsのPowerShellにも同等の機能が内蔵されています。 APIペイロードの素早い確認には、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 -DツールをインストールせずにJWTトークンを検査するには、トークンを ToolDeckのBase64 Decoder に貼り付け、ドットで分割して各パートをデコードしてください。
高パフォーマンスの代替: encoding/base64 はすでに高速
Pythonでは orjson と json のパフォーマンス比較が重要な話題ですが、 Goの encoding/base64 はすでにアセンブリ最適化されており、大半のワークロードで 十分に高速です。ただし、タイトなループで数百万件のレコードを処理する場合は、 filippo.io/base64 がドロップイン互換のAPIでSIMDアクセラレートされたデコードを提供します。
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}
}パフォーマンス向上はAVX2サポートのamd64で最も顕著で、大容量入力では2〜4倍のスループット改善が ベンチマークで示されています。日常的なAPIレスポンスのデコード (1回あたり数百バイト程度) では 標準ライブラリをそのまま使用してください。
GoでのBase64 JWTペイロードのデコード
JWTの検査はほぼすべてのバックエンドサービスで必要になります。デバッグセッションの大半は 「このトークンには実際に何が入っているのか?」という問いに行き着きます。JWTライブラリを導入しなくても その答えは得られます。トークンはドットで区切られた3つのBase64urlエンコードセグメントで構成されており、 中間のセグメントが実際に必要なペイロードです。
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]
}よくあるミス
これらの4つのミスはすべて実際のコードレビューで遭遇しました。特に最初の2つは、新しい認証プロバイダーを 統合するたびにほぼ必ずと言っていいほど出てきます。
問題: JWTトークンやOAuthトークンはURLセーフなBase64アルファベット (- と _) を使用します。これらを StdEncoding.DecodeString に渡すと 'illegal base64 data' エラーが発生します。
解決策: 入力元を確認してください。認証システムのトークンには RawURLEncoding を、バイナリ添付ファイルには 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: nil問題: Decode は事前に確保したバッファに書き込み、実際に書き込まれたバイト数を返します。DecodedLen は上限値を返すため、バッファの末尾にはゴミバイトが残る場合があります。
解決策: 結果は常に dst[:n] でスライスしてください。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 bytes問題: ターミナル、メール、設定ファイルからコピーしたBase64文字列には末尾の改行やスペースが含まれることがよくあります。そのまま DecodeString に渡すと空白文字の位置でエラーになります。
解決策: デコード前に strings.TrimSpace (埋め込まれた改行には strings.ReplaceAll も) を呼び出してください。
// 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
問題: バイナリデータ (画像や圧縮ペイロード) に対して string(decoded) を呼び出すと、不正なUTF-8文字列が生成されます。Goの文字列は任意のバイトを保持できますが、一部の操作でデータが破損することがあります。
解決策: パイプライン全体でバイナリデータは []byte として扱ってください。string(decoded) はデコード内容がテキストであることが保証されている場合のみ呼び出してください。
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)メソッド比較
すべてのバリアントは標準ライブラリに含まれており、外部依存は一切不要です。
JWTトークンとOAuthフローには RawURLEncoding。メール添付ファイルとMIMEデータには StdEncoding。ディスクやネットワークからの大容量バイナリファイルにはリーダーを base64.NewDecoder でラップすれば、ファイルサイズに依存せずメモリ使用量を一定に保てます。 カスタムアルファベットが必要な場合は、base64.NewEncoding(alphabet) で新しいエンコーディング オブジェクトを作成できます。
開発中に手軽に確認したい場合は、 オンラインBase64 Decoder を使う方が、Goプログラムを起動するより素早く処理できます。
よくある質問
GoでBase64文字列をデコードするには?
encoding/base64 をインポートし、base64.StdEncoding.DecodeString(s) を呼び出します。戻り値は ([]byte, error) なので、必ずエラーを確認してください。文字列がURLセーフ文字 (+ と / の代わりに - と _) を使用している場合は、base64.URLEncoding.DecodeString を使用してください。JWTトークンや最近のAPIの多くには RawURLEncoding (パディングなし) が適切です。
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"}
}GoのStdEncodingとURLEncodingの違いは何ですか?
StdEncoding は RFC 4648 §4 で定義された標準Base64アルファベットを使用し、+ と / の文字と = パディングを含みます。URLEncoding は RFC 4648 §5 で定義されており、+ を - に、/ を _ に置き換えることで、パーセントエンコードなしにURLやHTTPヘッダーで安全に使用できます。JWTトークン、OAuthトークン、クエリ文字列に埋め込むデータには URLEncoding を使用してください。
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 =)
}Goで "illegal base64 data" エラーを修正するには?
このエラーは、入力に期待されるアルファベット外の文字が含まれているか、パディングが正しくないことを意味します。よくある原因は3つあります。URLセーフな入力に StdEncoding を使用している (URLEncoding に変更)、パディングありのエンコーダーをパディングなしの入力に使用している (RawStdEncoding または RawURLEncoding に変更)、または末尾の空白・改行が含まれている場合です。デコード前に strings.TrimSpace で空白を除去してください。
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
}GoでBase64エンコードされた大きなファイルをストリームデコードするには?
base64.NewDecoder(base64.StdEncoding, reader) を使用すると、任意の io.Reader をラップしてオンザフライでデコードできます。io.Copy と組み合わせることで、ファイル全体をメモリにバッファリングせずに書き込み先にパイプできます。これはBase64エンコードされたバイナリ添付ファイルや大容量データペイロードをデコードする標準的なパターンです。
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)
}GoでJWTライブラリなしにBase64 JWTペイロードをデコードできますか?
できます。JWTはドットで区切られた3つのBase64urlエンコードセグメントで構成されています。"." で分割し、2番目のセグメント (インデックス1) を base64.RawURLEncoding.DecodeString でデコードします。JWTのヘッダーとペイロードはURLセーフアルファベットでパディングなしです。署名セグメント (インデックス2) はバイナリであり、通常は検証にのみ必要です。
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"}
}HTTP APIレスポンスのBase64データをデコードするにはどのエンコーディングを使えばいいですか?
APIのドキュメントを確認するか、エンコードされた文字列を検査してください。+ や / が含まれ末尾が = で終わる場合は StdEncoding を使用します。- や _ を使用し = がない場合は RawURLEncoding を使用します。不明な場合はまず RawURLEncoding を試してください。現代的なAPIの多く (OAuth2、JWT、Google Cloud、AWS) はパディングなしのURLセーフBase64を使用しています。
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)
}関連ツール
- Base64 Encoder — バイナリデータやテキストをブラウザでBase64にエンコードします。GoのユニットテストにペーストするテストフィクスチャーをGoコードなしで生成するのに便利です。
- JWT Decoder — JWTの3つのセグメントを一度に分割・デコードし、フィールド単位でペイロードを確認できます。デバッグ中にトークンを読むだけなら、Goコードは一切不要です。
- URL Decoder — URLエンコードされた文字列をパーセントデコードします。APIレスポンスにBase64urlデータとパーセントエンコードされたクエリパラメーターが混在している場合に便利です。
- JSON Formatter — Base64のJWTペイロードやAPIレスポンスをデコードした後、JSONをここに貼り付けると構造をすぐにきれいに表示・検証できます。