Загрузить файл
  1. base64hub
  2. Base64 в коде
  3. Go (Golang)
Код

Base64 в Go

В пакете encoding/base64 есть четыре готовые кодировки и потоковые обёртки. Большая часть работы сводится к выбору нужной. Результаты ниже получены на Go 1.25.

Закодировать и декодировать строку

Строки в 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 может вернуть вместе с ней уже декодированные байты, и без проверки вы молча получите обрезанные данные.

Четыре кодировки: 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: он дописывает последние один-два байта и дополнение. Забытый Close() – классическая ошибка с Base64 в Go: вывод выглядит нормально, но в нём не хватает до четырёх символов. Декодирование устроено симметрично:

r := base64.NewDecoder(base64.StdEncoding, f)
io.Copy(dst, r)

На тестовом файле в 300 КБ оба направления совпали с функциями, работающими в памяти. Декодер пропускает \r и \n, поэтому сразу читает данные с переносами строк в стиле MIME. Чтобы заранее вычислить размер буфера, есть EncodedLen: 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 (символ _)
aGkillegal base64 data at input byte 0
aGk=!!illegal base64 data at input byte 4
aGk==illegal base64 data at input byte 4
aGVs bG8Killegal base64 data at input byte 4 (пробел)
aGl=ошибки нет, возвращает hi
"aGVs\nbG8K"ошибки нет, перевод строки пропущен

У aGl= ненулевые биты дополнения. Чтобы отвергать такие неканонические строки, используйте .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.5.
  • Strict(): Go 1.8.
  • AppendEncode, AppendDecode: Go 1.22.
  • Кодировщика с переносом строк в стиле MIME в пакете нет; с почтой работают пакеты mime и net/mail, или вставляйте \r\n через каждые 76 символов сами.

Вопросы и ответы

Чем URLEncoding отличается от RawURLEncoding?

Обе используют URL-safe алфавит с - и _. URLEncoding пишет дополнение = и требует его при декодировании; RawURLEncoding дополнение не пишет и отвергает. JWT используют RawURLEncoding.

Что значит «illegal base64 data at input byte 0»?

Это base64.CorruptInputError, а число – смещение, на котором декодирование сломалось. Байт 0 в короткой строке обычно означает неверную длину, чаще всего пропущенное дополнение; для строк без дополнения берите кодировку Raw.

Игнорирует ли декодер Base64 в Go переводы строк?

Да. \r и \n пропускают все четыре кодировки, поэтому строки, перенесённые в стиле MIME, декодируются. Пробелы и другие символы – ошибка.

Читайте также