文字列のエンコードとデコード
Go の文字列はもともと UTF-8 なので、[]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
}
err は必ずチェックしてください。失敗時、DecodeString はそこまでにデコードしたバイト列をエラーと一緒に返すことがあるため、チェックせずに結果を使うと、知らないうちに途中で切れたデータを扱うことになります。
4 つのエンコーディング:Std、URL、Raw
| 変数 | アルファベット | パディング | <<???>> のエンコード結果 |
|---|---|---|---|
StdEncoding | + / | あり | PDw/Pz8+Pg== |
URLEncoding | - _ | あり | PDw_Pz8-Pg== |
RawStdEncoding | + / | なし | PDw/Pz8+Pg |
RawURLEncoding | - _ | なし | 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
デコードは、パディングについてどちらの方向にも厳格です。パディング付きのエンコーディングはパディングのない入力を拒否し、Raw 系は = を拒否します。
base64.StdEncoding.DecodeString("aGk")
// illegal base64 data at input byte 0
base64.RawURLEncoding.DecodeString("PDw_Pz8-Pg==")
// illegal base64 data at input byte 10
複数のソースから入力を受け付けるなら、strings.TrimRight(s, "=") で末尾の = を取り除いてから Raw のエンコーディングでデコードしてください。base64.NewEncoding(alphabet) と .WithPadding(base64.NoPadding) で独自のエンコーディングを作ることもできます。それぞれのバリアントが使われる場面は Base64 と Base64URL の違い で説明しています。
ファイルのエンコードとデコード
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)
NewEncoder と NewDecoder によるストリーム処理
大きなファイルは io.Writer か io.Reader でラップします。メモリ使用量は一定に保たれます。
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() は out を閉じるのではなく、最後の 1〜2 バイトとパディングを書き出します。これを忘れるのが Go の Base64 の典型的なバグで、出力は一見正常なのに最大 4 文字足りなくなります。デコードも対称的です。
r := base64.NewDecoder(base64.StdEncoding, f)
io.Copy(dst, r)
300 KB のテストファイルでは、ストリーミングとメモリ上の処理の結果が同一でした。デコーダーは \r と \n を読み飛ばすので、MIME 形式で折り返された入力を直接読めます。バッファサイズを事前に計算するには、EncodedLen(5) が 8 を、RawStdEncoding.EncodedLen(5) が 7 を返します。その裏にある式は サイズ増加の解説 にあります。
Go 1.22 では、既存のスライスに追記する AppendEncode と AppendDecode が追加されました。ヘッダーを作るときに便利です。
dst := base64.StdEncoding.AppendEncode([]byte("Basic "), []byte("user:pass"))
fmt.Println(string(dst)) // Basic dXNlcjpwYXNz
Basic Auth ヘッダージェネレーター なら、同じ値をブラウザで作れます。
検証と CorruptInputError
デコードのエラーはすべて base64.CorruptInputError で、最初の不正なバイトのオフセットを表す int64 です。
_, 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
}
StdEncoding での結果:
| 入力 | エラー |
|---|---|
PDw_Pz8-Pg== | illegal base64 data at input byte 3(_ の位置) |
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(スペース) |
aGl= | エラーなし、hi を返す |
"aGVs\nbG8K" | エラーなし、改行は読み飛ばす |
aGl= はパディングビットが 0 ではありません。このような正規形でない入力を拒否するには .Strict() を使います。
_, err = base64.StdEncoding.Strict().DecodeString("aGl=")
// illegal base64 data at input byte 3
Base64 バリデーター も同じ位置を強調表示します。よくある原因は よくある Base64 のエラー にまとめています。
落とし穴
- JWT のパートを
StdEncodingでデコードする。 トークンはパディングなしの Base64URL です。RawURLEncodingを使うか、まず JWT デコーダー でトークンを確認してください。 - 途中までの結果を見落とす。
"aGk=!!"は"hi"とエラーの両方を返します。err != nilのときはバイト列を使わないでください。 - エンコーダーの
Close()を忘れる。 最後のブロックが失われます。 - コピー&ペーストで紛れ込んだスペース。 デコーダーが読み飛ばすのは
\rと\nだけです。それ以外の空白は、先にstrings.Join(strings.Fields(s), "")で取り除いてください。
バージョンごとの注意
RawStdEncodingとRawURLEncoding:Go 1.5Strict():Go 1.8AppendEncode、AppendDecode:Go 1.22- このパッケージには MIME の折り返しを行うエンコーダーはありません。メールは
mimeとnet/mailパッケージで扱うか、76 文字ごとに自分で\r\nを挿入してください。
よくある質問
URLEncoding と RawURLEncoding の違いは何ですか?
どちらも - と _ を使う URLセーフなアルファベットです。URLEncoding は = のパディングを付け、デコード時にも要求します。RawURLEncoding はパディングを省略し、付いていると拒否します。JWT は RawURLEncoding を使います。
「illegal base64 data at input byte 0」とはどういう意味ですか?
base64.CorruptInputError で、数値はデコードに失敗したオフセットです。短い文字列でバイト 0 と出る場合、たいていは長さの誤り、多くはパディングの不足が原因です。パディングのない入力には Raw のエンコーディングを使ってください。
Go の Base64 デコーダーは改行を無視しますか?
はい。4 つのエンコーディングすべてが \r と \n を読み飛ばすので、MIME 形式で折り返された入力もデコードできます。スペースやその他の文字はエラーになります。