마지막 문자에 쓰이지 않는 비트가 있습니다
RFC 4648 §3.5는 올바른 인코더가 패드 비트를 0으로 설정하도록 요구합니다. 1바이트 입력은 두 번째 Base64 문자의 상위 2비트만 사용하므로 나머지 4비트는 패드 비트입니다. 문자 g의 인덱스 32는 100000, h의 인덱스 33은 100001입니다. f의 바이트는 01100110이며 두 표현에서 실제 데이터에 쓰이는 비트는 같습니다. Zg==는 정준 출력이고 Zh==는 0이 아닌 패드 비트를 가집니다. 끝의 = 문자와 버려지는 비트를 혼동하지 마세요.
실제 디코더와 재인코딩을 확인하세요
WHATWG Infra의 관대한 Base64 디코딩은 남은 12비트에서 마지막 4비트, 18비트에서 마지막 2비트를 버립니다. HTML의 atob는 이 알고리즘을 사용합니다. Moyoutil은 atob로 바이트를 복원하고 UTF-8 텍스트로 해석합니다. 직접 실행한 결과 Zg==와 Zh==는 모두 f, Zm8=와 Zm9=는 모두 fo였습니다. 다시 인코딩하면 각각 Zg==와 Zm8=가 됩니다. 아래 코드는 바이트를 직접 재인코딩하며 임의 바이너리를 UTF-8 텍스트로 바꾸지 않습니다.
for (const input of ['Zg==', 'Zh==', 'Zm8=', 'Zm9=']) {
const bytes = atob(input);
const canonical = btoa(bytes);
console.log(input, bytes, canonical, input === canonical);
}
// Zg== f Zg== true
// Zh== f Zg== false
// Zm8= fo Zm8= true
// Zm9= fo Zm8= false같은 바이트라는 사실과 같은 원문 문자열이라는 사실은 다릅니다. 캐시 키나 비교 기준을 설계할 때 필요한 동등성을 먼저 정하세요. 받는 프로토콜이 정준 표현을 요구한다면 그 규칙대로 검증해야 합니다. 서명된 문자열을 검증하기 전에 임의로 바꾸는 방법을 권하지 않습니다. 이 예제의 재인코딩 비교는 패딩 있는 표준 Base64에 한정되며 모든 프로토콜의 검증기를 대신하지 않습니다.
자주 묻는 질문
모든 디코더가 Zh==를 허용하나요?
아닙니다. RFC 4648은 0이 아닌 패드 비트의 거부를 허용하며 이를 참조하는 규격이 동작을 정할 수 있습니다. 여기서 확인한 결과는 HTML atob와 현재 Moyoutil 구현의 동작입니다.