문자열 인코딩과 디코딩
printf '%s' 'héllo wörld' | base64 # aMOpbGxvIHfDtnJsZA==
printf '%s' 'aMOpbGxvIHfDtnJsZA==' | base64 -d # héllo wörld
base64는 바이트를 인코딩하므로 결과는 터미널의 문자 인코딩을 따릅니다. 요즘 Linux나 macOS 환경이라면 UTF-8입니다.
가장 흔한 실수는 줄 바꿈을 덧붙이는 echo입니다.
echo 'héllo wörld' | base64 # aMOpbGxvIHfDtnJsZAo= (note the "o=")
echo -n hi | base64 # aGk=
echo -n은 Bash에서는 동작하지만 모든 sh에서 동작하지는 않습니다. printf '%s'는 이식성이 있습니다. here-string(base64 <<< "text")도 줄 바꿈을 붙입니다. 누군가 보낸 Base64가 Cg==나 o=로 끝난다면 끝에 줄 바꿈이 들어 있을 가능성이 높습니다. Base64 디코더에 붙여넣어 확인해 보세요.
GNU(Linux)와 BSD(macOS)의 옵션 차이
| 작업 | GNU coreutils (Linux) | macOS |
|---|---|---|
| 디코딩 | -d, --decode | -D, --decode (최근 버전은 -d도 가능) |
| 줄 바꿈 없음 | -w 0 | 기본값 |
| N자에서 줄 바꿈 | -w N (기본값 76) | -b N |
| 입력 파일 | base64 file | base64 -i file 또는 base64 < file |
| 출력 파일 | 리디렉션 > | -o file 또는 리디렉션 |
| 디코딩 시 잡음 무시 | -i, --ignore-garbage | 없음 |
두 -i 옵션은 의미가 다릅니다. macOS에서는 입력 파일을 지정하고, Linux에서는 잡음을 무시합니다. 이식성 있는 스크립트는 stdin에서 읽고 stdout으로 써야 합니다.
base64 < in.bin | tr -d '\n' > out.b64 # same output on Linux and macOS
base64 --decode < out.b64 > in-copy.bin
GNU base64는 기본적으로 76자에서 줄을 바꾸는데, 이 때문에 헤더와 환경 변수가 깨집니다.
head -c 60 /dev/zero | tr '\0' x | base64 -w 0
# eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
-w 0이 없으면 첫 줄이 76자이고 나머지는 두 번째 줄로 넘어갑니다. MIME 형식의 줄이 필요하다면 기본 줄 바꿈이 바로 원하는 동작입니다.
URL-safe Base64
GNU coreutils 8.31 이상에는 Base64URL을 지원하는 basenc가 들어 있습니다. 패딩은 유지합니다.
printf '%s' '<<???>>' | basenc --base64url # PDw_Pz8-Pg==
printf '%s' 'PDw_Pz8-Pg' | basenc --base64url -d # <<???>>
macOS나 오래된 Linux에서는 tr로 문자를 바꿉니다.
printf '%s' '<<???>>' | base64 | tr '+/' '-_' | tr -d '=' # PDw_Pz8-Pg
디코딩할 때는 반대로 바꾸고, 엄격한 디코더를 위해 패딩도 되돌려 놓아야 합니다.
s=PDw_Pz8-Pg
s=$(printf '%s' "$s" | tr -- '-_' '+/')
while [ $(( ${#s} % 4 )) -ne 0 ]; do s="$s="; done
printf '%s' "$s" | base64 -d # <<???>>
--는 tr이 -_를 옵션으로 읽지 않게 막습니다. JWT에도 똑같은 변환이 필요하며, JWT 디코더가 이를 대신 처리합니다. 자세한 내용은 Base64와 Base64URL 비교에 있습니다.
파일 인코딩과 디코딩
base64 -w 0 hello.txt # aGVsbG8K
base64 -d <<< 'aGVsbG8K' > copy.txt
base64 -d payload.b64 > payload.pdf
base64는 블록 단위로 읽고 쓰는 스트리밍 방식이라 파일 크기는 문제가 되지 않습니다. 이미지로 Data URI를 만들려면 다음과 같이 합니다.
printf 'data:image/png;base64,%s' "$(base64 -w 0 logo.png)"
HTTP Basic Auth 헤더는 이렇게 만듭니다.
printf 'Basic %s' "$(printf '%s' 'user:pass' | base64)" # Basic dXNlcjpwYXNz
Basic Auth 헤더 생성기를 쓰면 셸 기록에 비밀번호를 남기지 않고 같은 결과를 얻을 수 있습니다.
검증, 오류, 종료 코드
GNU base64 -d는 잘못된 문자를 만나면 stderr에 base64: invalid input을 출력하고 상태 1로 종료합니다. 그래도 그 지점까지 디코딩한 내용은 모두 쓰므로, 디코딩에 실패하면 일부만 쓰인 파일이 남을 수 있습니다.
printf 'aGk=!!' | base64 -d # prints "hi", then: base64: invalid input
echo $? # 1 (without a pipe after it)
printf 'aGk=!!' | base64 -d -i # hi (garbage ignored, exit 0)
coreutils 9.7에서의 결과:
| 입력 | 결과 |
|---|---|
aGk | hi, 종료 코드 0 (패딩 누락 허용) |
"aGVs\nbG8K" | hello, 종료 코드 0 (줄 바꿈 무시) |
aGl= | hi 다음에 invalid input, 종료 코드 1 |
a | invalid input, 종료 코드 1 |
PDw_Pz8-Pg== | 일부 출력, invalid input, 종료 코드 1 |
종료 상태로 검증하고, 실패해도 멀쩡한 데이터를 덮어쓰지 않도록 임시 파일에 디코딩하세요.
if printf '%s' "$b64" | base64 -d > /tmp/out.bin 2>/dev/null; then
mv /tmp/out.bin result.bin
else
echo "invalid Base64" >&2
fi
파이프라인에서 $?는 마지막 명령의 상태입니다. base64 -d가 마지막이 아니라면 set -o pipefail을 추가하세요. 로그나 이메일에서 가져온 문자열이 디코딩되지 않으면 Base64 복구가 따옴표, 공백, 접두사를 제거해 줍니다. 각 실패 원인은 오류 가이드에서 설명합니다.
명령줄에서 쓸 수 있는 다른 도구
openssl base64는 거의 모든 시스템에 있고 Linux와 macOS에서 똑같이 동작합니다. OpenSSL로 Base64 다루기를 참고하세요.- coreutils가 없는 곳에서는
python3 -m base64 -d file과python3 -m base64 file을 쓸 수 있습니다. 출력은 76자에서 줄 바꿈됩니다. - Kubernetes에서는
kubectl get secret x -o jsonpath='{.data.password}' | base64 -d가 흔한 패턴이며, Kubernetes 시크릿 디코더를 써도 됩니다.
자주 묻는 질문
echo hello | base64 결과가 온라인 도구와 다른 이유는 무엇인가요?
echo가 줄 바꿈을 붙이므로 hello(aGVsbG8=)가 아니라 hello\n(aGVsbG8K)을 인코딩하게 됩니다. printf '%s' hello나 echo -n hello를 사용하세요.
base64 출력이 줄 바꿈되지 않게 하려면 어떻게 하나요?
Linux에서는 base64 -w 0을 씁니다. macOS는 기본적으로 줄을 바꾸지 않습니다. 양쪽에서 똑같이 동작하는 명령이 필요하면 tr -d '\n'으로 파이프하세요.
'base64: invalid input'은 무슨 뜻인가요?
GNU base64 -d가 알파벳에 없는 문자나 잘못된 패딩을 만났다는 뜻입니다. 그때까지 디코딩한 바이트를 출력한 뒤 오류를 표시하고 상태 1로 종료합니다. 흔한 원인은 URL-safe 문자 -와 _, 따옴표, data: 접두사입니다.
macOS의 base64 -d는 Linux와 같나요?
예전 macOS는 -D(또는 --decode)만 알았고, 최근 버전은 -d도 받습니다. 양쪽에서 돌아가야 하는 스크립트에는 --decode를 쓰세요.