![]()
Python 3.15에서 Base64/32 패딩 없이도 더 잘 된다: “len%4==0” 지옥을 끝내는 실전 디코딩 패턴
meta_description: Base64/32 디코딩에서 가장 흔한 실수는 패딩(=) 누락 처리다. CPython은 PR #147974(gh-73613)로 Base32/Base64의 “패딩 없는 입력”을 더 잘 다루도록 개선했고, 문서/테스트도 함께 보강됐다(3.15 WhatsNew 포함). 이 글은 패딩이 왜 생기는지, 어디서 깨지는지, 그리고 지금 당장(3.11~) 안전하게 운영하는 호환 디코딩 함수까지 실전 관점으로 정리한다. meta_keywords: python,base64,base32,padding,urlsafe,decode,encoding,binascii,token,jwt,webhook,compat,python 3.15,whatsnew,security,validation,error handling meta_robots: index,follow
Base64 디코딩하다가 한 번쯤은 이런 코드가 나왔을 거다.
s += "=" * (-len(s) % 4)
문제는 이게 “잘 되는 것처럼 보이는데, 종종 사고를 만든다”는 점이다.
- 어떤 입력은 Base64가 아니라 그냥 쓰레기인데도 통과해버리고
- 어떤 입력은 URL-safe 변형인데 표준 Base64로 처리하다가 깨지고
- 어떤 입력은 패딩이 없어도 정상인 케이스인데, 서비스마다 처리가 달라진다
최근 CPython PR 하나가 이 불편함을 정확히 겨냥했다.
- gh-73613: Base32/Base64에서 패딩 없는 입력을 지원/개선
- CPython PR #147974 (merged 2026-04-04)
이 글은 “새 기능 소개”보다, 운영에서 바로 쓰는 패턴 위주로 정리한다.
![]()
1) 패딩(=)은 왜 생기고, 왜 자꾸 빠지나
Base64는 3바이트를 4글자로 바꾼다.
그래서 입력 길이가 3의 배수가 아닐 때 마지막을 =로 채워 길이를 맞춘다.
- 길이 mod 4가 0이 아니면 디코딩이 실패할 수 있다
그런데 패딩이 빠지는 이유는 실무에서 꽤 다양하다.
- URL 파라미터/Path로 전달되며
=가 잘리거나 인코딩되기 싫어서 제거 - JWT처럼 “base64url(패딩 제거)”이 사실상 관례인 포맷
- 클라이언트가 이미 패딩을 제거한 값을 저장/전달
결과적으로 서버는 “패딩이 없는 Base64”를 자주 만나게 된다.
2) CPython PR #147974가 바꾼 방향(요약)
PR 자체는 Base64만이 아니라 Base32까지 포함한다.
diff를 보면 다음이 같이 움직인다.
Lib/base64.py구현Modules/binascii.c저수준 처리Doc/library/base64.rst,Doc/library/binascii.rst문서Doc/whatsnew/3.15.rstWhat’s New- 테스트 보강 (
Lib/test/test_base64.py,Lib/test/test_binascii.py)
즉, “우연히 되게 한 변경”이 아니라 문서/테스트까지 포함해서 의도적으로 다룬 개선이다.
핵심은 이거다.
- 패딩이 없다고 해서 무조건 에러를 내기보다
- 정상 범위에서 “패딩 없는 입력”을 더 잘 처리할 수 있게 만드는 방향
(정확한 동작 범위는 3.15에 들어간 최종 문서/릴리스 노트를 기준으로 확인하는 게 안전하다.)
3) 지금 당장(3.11~) 안전하게 쓰는 디코딩 함수: “패딩 보정 + 검증”을 같이
실무에서 중요한 건 “일단 디코딩되면 됨”이 아니라,
- 잘못된 입력을 얼마나 잘 걸러내는지
- URL-safe인지 표준인지 혼동하지 않는지
다.
아래는 내가 서비스 코드에서 자주 쓰는 형태다.
(A) base64url 토큰용 (JWT/URL 파라미터 계열)
import base64
import binascii
def b64url_decode_strict(s: str) -> bytes:
"""URL-safe Base64 디코딩 (패딩 누락 허용), 잘못된 입력은 예외.
- URL-safe 변형(-, _)만 허용
- 길이를 패딩으로 보정
- validate=True로 문자 검증(가능한 경우)
"""
if not isinstance(s, str):
raise TypeError("expected str")
# URL-safe alphabet만 허용하고 싶으면 여기서 사전 검증을 둘 수도 있음
# (서비스 성격에 따라)
pad = (-len(s)) % 4
if pad:
s = s + ("=" * pad)
try:
# Python의 urlsafe_b64decode는 내부적으로 b64decode를 씀
# validate 플래그를 직접 주려면 표준 b64decode로 bytes를 넘기는 방식이 더 명확함
return base64.b64decode(s.encode("ascii"), altchars=b"-_", validate=True)
except (UnicodeEncodeError, binascii.Error) as e:
raise ValueError("invalid base64url") from e
포인트는 두 개다.
- 패딩 보정은 하되, validate=True로 문자 검증을 같이 한다
- URL-safe인 경우
altchars=b"-_"를 명시해서 혼동을 줄인다
(B) 표준 Base64(파일/바이너리/일반 API)
import base64
import binascii
def b64_decode_strict(s: str) -> bytes:
if not isinstance(s, str):
raise TypeError("expected str")
pad = (-len(s)) % 4
if pad:
s = s + ("=" * pad)
try:
return base64.b64decode(s.encode("ascii"), validate=True)
except (UnicodeEncodeError, binascii.Error) as e:
raise ValueError("invalid base64") from e
이 두 함수를 만들어두면, 서비스 전반에서 “대충 패딩 붙여서 decode”를 반복하지 않게 된다.
4) validate=True를 왜 추천하나(그리고 어디서 터지나)
Base64는 인코딩 자체가 “암호”가 아니라 “표현 방식”이다. 그래서 입력 검증을 제대로 안 하면, 공격/오류가 그냥 통과하는 모양새가 나온다.
base64.b64decode(..., validate=True)를 켜면 좋은 점은 단순하다.
- Base64 알파벳 밖의 문자가 섞이면 예외로 처리된다
- “디코딩은 됐는데 내용이 이상함” 같은 버그를 초기에 걸러낸다
다만 현실적인 주의점도 있다.
- 일부 데이터는 줄바꿈/공백을 섞어서 전송되기도 한다(메일/레거시 시스템)
- 이 경우
validate=True는 더 엄격하게 실패할 수 있다
그래서 운영에서는 둘 중 하나를 선택해야 한다.
- API/토큰 입력(웹훅, JWT, URL 파라미터): 엄격 모드(validate=True)가 맞다
- 파일/레거시 입력(줄바꿈 포함 가능): 사전 정규화(공백 제거) 후 디코딩을 고려
즉, “패딩 보정”만 해서는 부족하고, 어떤 채널의 입력인지에 맞춰 검증 정책을 고정하는 게 핵심이다.
5) Base32도 같이 다룰 때(2FA/시크릿 키에서 자주 만난다)
Base32는 TOTP 같은 시크릿 키에서 자주 보인다. 그리고 이것도 마찬가지로 패딩이 빠진 값이 돌아다닌다.
- 어떤 앱은 패딩을 생략하고
- 어떤 구현은 대문자/소문자/공백 처리에서 차이가 난다
이번 CPython 변경이 Base32까지 같이 다룬 이유가 여기 있다.
실무에서는 Base32를 디코딩할 때도 규칙을 정해두는 게 좋다.
- 공백/하이픈을 제거할지
- 대소문자를 허용할지
- 패딩을 보정할지
(여기서도 “한 번에 완벽”이 아니라, 테스트 케이스 3개를 먼저 만드는 게 제일 빠르다.)
![]()
6) 흔한 실패 패턴 5개(운영에서 자주 터진다)
1) 표준 Base64와 base64url을 섞는다
- +// vs -/_ 차이를 무시하면 간헐적으로 깨진다.
2) 패딩만 붙이고 검증을 안 한다 - 쓰레기 입력이 “우연히” 디코딩되는 케이스가 생긴다.
3) 디코딩 실패를 500으로 만든다 - 입력 검증 실패는 보통 400 계열이 맞다(서비스 규약에 맞게).
4) bytes/str을 섞어 처리한다 - ASCII 강제/예외 처리 없이 섞으면 에러 처리가 지저분해진다.
5) 테스트 케이스가 없다 - “패딩 있음/없음”, “표준/url-safe”, “잘못된 문자 포함” 3종만 있어도 운영 사고가 줄어든다.
7) 팀 코드베이스에 적용하는 순서(20분)
1) b64url_decode_strict / b64_decode_strict를 공용 유틸로 만든다
2) 프로젝트에서 "=" * (-len(s)%4) 패턴을 grep 해서 교체한다
3) 입력 타입(표준/url-safe)을 API 스펙으로 고정한다
4) “잘못된 입력” 테스트를 3개만 추가한다
이렇게 하면 Python 버전이 올라가든(3.15든), 팀의 입력 검증 품질이 먼저 안정화된다.
Sources
- CPython PR #147974 — gh-73613: Support Base32 and Base64 without padding (merged 2026-04-04)
- https://github.com/python/cpython/pull/147974
Keywords
python,base64,base32,padding,urlsafe,decode,encoding,binascii,token,jwt,webhook,compat,python 3.15,whatsnew,security,validation,error handling
이미지 크레딧/라이선스
- Mobile developer at work (Unsplash).jpg — Parker Byrd / CC0
- https://commons.wikimedia.org/wiki/File:Mobile_developer_at_work_(Unsplash).jpg
- http://creativecommons.org/publicdomain/zero/1.0/deed.en
- Binary damage code (28227210988).jpg — Markus Spiske / CC0
- https://commons.wikimedia.org/wiki/File:Binary_damage_code_(28227210988).jpg
- http://creativecommons.org/publicdomain/zero/1.0/deed.en
댓글
댓글 쓰기