문자열 인코딩과 디코딩
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을 닫지 않습니다. 마지막 한두 바이트와 패딩을 기록할 뿐입니다. 이를 빠뜨리는 것이 Go Base64의 전형적인 버그입니다. 출력은 멀쩡해 보이지만 최대 네 문자가 모자랍니다. 디코딩도 대칭적입니다.
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
모든 디코딩 오류는 첫 번째 잘못된 바이트의 int64 오프셋인 base64.CorruptInputError입니다.
_, 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 오류에 있습니다.
함정
StdEncoding으로 JWT 부분 디코딩. 토큰은 패딩 없는 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패키지가 처리하며, 아니면 76자마다 직접\r\n을 넣으세요.
자주 묻는 질문
URLEncoding과 RawURLEncoding은 무엇이 다른가요?
둘 다 -와 _를 쓰는 URL-safe 알파벳을 사용합니다. URLEncoding은 = 패딩을 붙이고 디코딩할 때도 패딩을 요구하며, RawURLEncoding은 패딩을 생략하고 패딩이 있으면 거부합니다. JWT는 RawURLEncoding을 씁니다.
'illegal base64 data at input byte 0'은 무슨 뜻인가요?
base64.CorruptInputError이며, 숫자는 디코딩이 실패한 위치(오프셋)입니다. 짧은 문자열에서 바이트 0이라면 대개 길이가 잘못되었다는 뜻이고, 패딩 누락인 경우가 많습니다. 패딩 없는 입력에는 Raw 인코딩을 사용하세요.
Go의 Base64 디코더는 줄 바꿈을 무시하나요?
네. 네 가지 인코딩 모두 \r과 \n을 건너뛰므로 MIME 형식으로 줄 바꿈된 입력도 디코딩됩니다. 공백과 그 밖의 문자는 오류입니다.