문자열 인코딩과 디코딩
Python의 Base64 함수는 바이트를 다루므로, 텍스트는 먼저 UTF-8로 인코딩하고 마지막에 다시 디코딩해야 합니다. 둘 중 하나를 잊는 것이 가장 흔한 실수입니다.
import base64
s = "héllo wörld"
encoded = base64.b64encode(s.encode("utf-8")).decode("ascii")
print(encoded) # aMOpbGxvIHfDtnJsZA==
decoded = base64.b64decode(encoded).decode("utf-8")
print(decoded) # héllo wörld
b64decode는 str과 bytes를 모두 받지만, b64encode는 바이트 계열 객체만 받습니다. 문자열을 넘기면 TypeError: a bytes-like object is required, not 'str'이 발생합니다.
결과를 직접 확인하려면 Base64 디코더에 붙여넣으세요. 브라우저 안에서 로컬로 실행됩니다.
패딩 있는 URL-safe Base64와 패딩 없는 URL-safe Base64
URL-safe 알파벳은 +를 -로, /를 _로 바꿉니다(Base64와 Base64URL 비교 참고). Python은 = 패딩을 유지하므로, 대상 형식(JWT, 많은 API)이 패딩을 원하지 않으면 직접 제거하세요.
data = b"<<???>>"
print(base64.b64encode(data)) # b'PDw/Pz8+Pg=='
print(base64.urlsafe_b64encode(data)) # b'PDw_Pz8-Pg=='
print(base64.urlsafe_b64encode(data).rstrip(b"=")) # b'PDw_Pz8-Pg'
패딩 없는 입력을 디코딩하려면 패딩을 되돌려 놓아야 합니다. Python은 길이가 4의 배수여야 한다고 고집하기 때문입니다.
def b64url_decode(s: str) -> bytes:
return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
print(b64url_decode("PDw_Pz8-Pg")) # b'<<???>>'
b64encode와 b64decode는 +/를 대체하는 2바이트 문자열인 altchars 인수도 받습니다. altchars=b"-_"는 URL-safe 함수와 같습니다.
print(base64.b64decode("PDw_Pz8-Pg==", altchars="-_")) # b'<<???>>'
파일 인코딩과 디코딩
메모리에 들어가는 파일은 바이트를 읽어 한 번에 인코딩하세요.
from pathlib import Path
b64 = base64.b64encode(Path("hello.txt").read_bytes()).decode("ascii")
print(b64) # aGVsbG8K
Path("hello-copy.txt").write_bytes(base64.b64decode(b64))
코드를 쓰지 않고 한 번만 변환하면 된다면 파일 Base64 변환 도구가 브라우저에서 같은 일을 합니다.
큰 파일과 스트리밍
Base64는 입력 3바이트를 출력 4문자에 대응시키므로, 마지막을 제외한 각 조각이 3바이트의 배수라면 스트림을 조각 단위로 인코딩할 수 있습니다. 그러면 중간에 패딩이 생기지 않고, 이어 붙인 출력이 한 번에 인코딩한 결과와 같아집니다.
CHUNK = 3 * 1024 * 256 # 768 KiB, a multiple of 3
with open("big.bin", "rb") as src, open("big.b64", "wb") as dst:
while chunk := src.read(CHUNK):
dst.write(base64.b64encode(chunk))
100 KB 무작위 파일에서 base64 -w 0과 대조해 확인했으며, 바이트 단위로 동일했습니다. 입력에 줄 바꿈이 없다면 디코딩도 4문자의 배수 크기의 조각으로 똑같이 할 수 있습니다.
MIME 줄 바꿈
이메일과 일부 레거시 시스템은 한 줄이 76자 이하이기를 요구합니다(RFC 2045). encodebytes가 그렇게 해 주며, base64.encode / base64.decode는 파일 객체 사이에서 같은 일을 합니다.
print(base64.encodebytes(b"x" * 60))
# b'eHh4eHh4...eHh4\neHh4\n' (76 chars, newline, rest, newline)
with open("big.bin", "rb") as src, open("big.mime", "wb") as dst:
base64.encode(src, dst)
with open("big.mime", "rb") as src, open("big.out", "wb") as dst:
base64.decode(src, dst)
끝의 줄 바꿈에 주의하세요. encodebytes는 항상 줄 바꿈을 하나 붙입니다. 온라인 버전은 MIME Base64 인코더를 참고하세요.
입력 검증
기본적으로 b64decode는 관대합니다. 디코딩하기 전에 알파벳 밖의 문자를 모두 버립니다. 이 때문에 버그가 숨겨집니다. 엄격하게 하려면 validate=True를 넘기세요.
import binascii
print(base64.b64decode("aGk=!!")) # b'hi' (junk ignored)
try:
base64.b64decode("aGk=!!", validate=True)
except binascii.Error as e:
print(e) # Only base64 data is allowed
validate=True에서는 줄 바꿈도 잘못된 문자로 취급하므로 MIME 줄 바꿈은 먼저 제거하세요. 보게 될 오류는 모두 binascii.Error(ValueError의 하위 클래스) 타입입니다.
| 입력 | 메시지 |
|---|---|
aGk | Incorrect padding |
a | Invalid base64-encoded string: number of data characters (1) cannot be 1 more than a multiple of 4 |
validate=True와 aGk== | Excess padding not allowed |
validate=True와 aGk=!! | Only base64 data is allowed |
각 오류의 원인은 Base64 오류 가이드에서 설명하고, Base64 검사기는 문자열이 어디에서 깨지는지 보여 줍니다.
흔한 함정
b64decode에 URL-safe 입력 넣기. validate=True가 없으면 -와 _ 문자가 조용히 버려지고, 오류 대신 쓰레기 값이 나옵니다.
print(base64.b64decode("PDw_Pz8-Pg==")) # b'<<\x0f\xcf\xc3\xe0'
print(base64.urlsafe_b64decode("PDw_Pz8-Pg==")) # b'<<???>>'
urlsafe_b64decode는 표준 입력도 받으므로, 어떤 알파벳을 받았는지 확실하지 않을 때 더 안전한 기본 선택입니다.
바이너리를 텍스트로 디코딩하기. 페이로드가 이미지나 ZIP이라면 .decode("utf-8")이 UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte로 실패합니다. 바이트로 유지해서 파일로 쓰세요.
출력의 b'...'. b64encode의 반환값을 출력하면 바이트 리터럴이 보입니다. 그 b'가 JSON이나 헤더에 들어갔다면 .decode("ascii")를 잊은 것입니다.
Base64를 보호 수단으로 취급하기. Base64는 인코딩이며 누구나 되돌릴 수 있습니다. 인코딩과 암호화의 차이를 참고하세요.
버전별 참고 사항
base64.b64encode,b64decode,urlsafe_*,altchars는 모든 Python 3 릴리스에 있습니다.b64decode는 Python 3.3부터 바이트뿐 아니라 ASCIIstr입력도 받습니다.encodestring과decodestring은 3.9에서 제거되었습니다.encodebytes와decodebytes를 사용하세요.binascii.b2a_base64(data, newline=False)(3.6 이상)는b64encode뒤에 있는 저수준 함수이며,b"hi"에 대해b'aGk='를 반환합니다.- 같은 모듈에는 다른 알파벳을 위한
b32encode,b16encode,b85encode,a85encode도 있습니다.
자주 묻는 질문
b64encode가 문자열 대신 b'...'를 반환하는 이유는 무엇인가요?
Python의 Base64 함수는 바이트를 받아 바이트를 반환합니다. str을 얻으려면 결과에 .decode("ascii")를 호출하고, 인코딩하기 전의 텍스트에는 .encode("utf-8")을 호출하세요.
Python에서 패딩 없는 Base64를 디코딩하려면 어떻게 하나요?
디코딩하기 전에 빠진 =를 다시 붙이세요. s + "=" * (-len(s) % 4). Python은 패딩 없는 입력을 binascii.Error: Incorrect padding으로 거부합니다.
b64decode와 urlsafe_b64decode는 무엇이 다른가요?
urlsafe_b64decode는 디코딩 전에 -와 _를 +와 /로 바꿉니다. 일반 b64decode는 validate=True를 넘기지 않으면 -와 _를 쓰레기 문자로 보고 조용히 버리므로, 오류 대신 잘못된 바이트가 나옵니다.
base64.encodestring을 아직 쓸 수 있나요?
없습니다. encodestring과 decodestring은 Python 3.9에서 제거되었습니다. 줄 바꿈이 있는 MIME 형식 출력에는 encodebytes와 decodebytes를 사용하세요.