문자열 인코딩과 디코딩
PHP 문자열은 바이트 문자열이므로 base64_encode는 문자열에 든 바이트를 그대로 인코딩합니다. 소스 파일을 UTF-8로 저장했다면 별도의 변환 단계 없이 UTF-8 Base64를 얻습니다.
<?php
$enc = base64_encode("héllo wörld");
echo $enc; // aMOpbGxvIHfDtnJsZA==
echo base64_decode($enc); // héllo wörld
텍스트가 다른 문자 집합(예: EUC-KR)을 쓰는 데이터베이스나 폼에서 온다면 먼저 mb_convert_encoding($s, 'UTF-8', 'Windows-1252')처럼 변환하세요. 그렇지 않으면 Base64는 그 바이트에 대해서는 올바르지만 다른 곳에서 디코딩하면 글자가 깨집니다. Base64 텍스트 변환 도구에서 결과에 여러 문자 집합을 적용해 볼 수 있습니다.
strict 모드: base64_decode($s, true)
두 번째 인수가 없으면 base64_decode는 알파벳에 없는 문자를 모두 버리고 나머지를 디코딩합니다. true를 주면 대신 false를 반환합니다.
var_dump(base64_decode("aGk=!!")); // string(2) "hi"
var_dump(base64_decode("aGk=!!", true)); // bool(false)
항상 ===로 확인하세요. 올바른 빈 입력은 ""로 디코딩되는데, 이 값은 거짓으로 평가되기 때문입니다.
$bytes = base64_decode($input, true);
if ($bytes === false) {
throw new InvalidArgumentException('Invalid Base64');
}
strict 모드는 이름만큼 엄격하지 않습니다. PHP 8.4에서 테스트한 결과입니다.
| 입력 | strict 결과 |
|---|---|
aGk (패딩 없음) | "hi" |
"aGk=\n" (끝 줄 바꿈) | "hi" |
aGl= (0이 아닌 패딩 비트) | "hi" |
aGk== (과도한 패딩) | false |
a (불가능한 길이) | false |
PDw_Pz8-Pg== (URL-safe 문자) | false |
정규 형식의 Base64만 받아야 한다면 공백을 제거하고 strict 모드로 디코딩한 다음 base64_encode($bytes) === $input을 확인하세요. Base64 검사기는 같은 검사를 하고 잘못된 문자를 짚어 줍니다.
패딩 있는 URL-safe Base64와 패딩 없는 URL-safe Base64
PHP에는 내장 Base64URL 함수가 없습니다. strtr로 두 문자를 바꾸세요. 문자 하나씩 대응시킬 때는 str_replace보다 빠릅니다.
function b64url_encode(string $data, bool $pad = false): string {
$s = strtr(base64_encode($data), '+/', '-_');
return $pad ? $s : rtrim($s, '=');
}
function b64url_decode(string $s): string|false {
return base64_decode(strtr($s, '-_', '+/'), true);
}
echo b64url_encode("<<???>>"); // PDw_Pz8-Pg
echo b64url_encode("<<???>>", true); // PDw_Pz8-Pg==
echo b64url_decode("PDw_Pz8-Pg"); // <<???>>
strict 모드가 패딩 누락을 허용하므로 디코딩 전에 =를 다시 붙일 필요가 없습니다. 차이점은 Base64와 Base64URL 비교에서 더 자세히 다룹니다.
파일 인코딩과 디코딩
echo base64_encode(file_get_contents("hello.txt")); // aGVsbG8K
file_put_contents("copy.txt", base64_decode("aGVsbG8K", true));
Data URI라면 MIME 타입을 앞에 붙이세요. 'data:image/png;base64,' . base64_encode($png). fileinfo가 활성화되어 있으면 mime_content_type($path)로 타입을 감지할 수 있습니다.
스트림 필터로 큰 파일 처리하기
file_get_contents는 파일 전체와 Base64 사본을 메모리에 올리므로 메모리 사용량이 파일 크기의 약 2.3배가 됩니다. convert.base64-encode 스트림 필터는 복사하면서 인코딩합니다.
$in = fopen("big.bin", "rb");
$out = fopen("big.b64", "wb");
stream_filter_append($out, "convert.base64-encode");
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);
디코딩도 같은 방식이며, php://filter URL로 한 줄에 할 수도 있습니다.
copy("php://filter/read=convert.base64-decode/resource=big.b64", "big.out");
300 KB 테스트 파일에서 둘 다 메모리 내 함수와 같은 출력을 냈습니다. 모든 조각이 3바이트의 배수라면, 예를 들어 fread($in, 3 * 8192)처럼 직접 반복문을 써도 됩니다. 다른 크기를 쓰면 출력 중간에 =가 끼어듭니다.
MIME 줄 바꿈
이메일 본문은 한 줄이 76자 이하여야 합니다. chunk_split이 바로 그 일을 하며, 기본 줄 끝은 \r\n입니다.
echo chunk_split(base64_encode(str_repeat("x", 60)));
// eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
// eHh4
끝에도 \r\n을 붙입니다. 메일 라이브러리는 이를 대신 처리해 줍니다. MIME Base64 인코더에서 형식을 볼 수 있습니다.
흔한 함정
- 신뢰할 수 없는 입력에 strict 모드를 잊는 것. 잘못된 데이터가 무작위 바이트로 디코딩되고, 원인과 동떨어진 곳에서 실패합니다.
- 기본 모드에 URL-safe 입력 넣기.
base64_decode("PDw_Pz8-Pg==")는<<???>>가 아니라string(6) "<<���"를 반환합니다. 먼저strtr로 변환하세요. null넘기기. PHP 8.1 이상에서는Deprecated: base64_encode(): Passing null to parameter #1 ($string) of type string is deprecated가 나옵니다. 배열을 넘기면TypeError: base64_encode(): Argument #1 ($string) must be of type string, array given이 발생합니다.+가 공백으로 바뀌는 문제. 쿼리 문자열 안의 표준 Base64는 URL 디코딩 과정에서 망가집니다. 그곳에서+는 공백을 뜻하기 때문입니다. URL-safe 변형이나rawurlencode를 사용하세요.- 비밀번호 저장소로 취급하기. Base64는 아무것도 숨기지 않습니다. 비밀번호에는
password_hash를 쓰고, 인코딩과 암호화의 차이를 참고하세요.
버전별 참고 사항
base64_encode,base64_decode,$strict플래그는 지원되는 모든 PHP 버전에 있습니다(strict 모드는 5.2에서 추가).- PHP 8.0부터
base64_decode는string|false를 반환하도록 선언되어 있으며, 배열이나 객체를 넘기면 경고 대신TypeError를 던집니다. convert.base64-encode필터는 MIME 출력을 위한line-length와line-break-chars옵션을 받습니다. 예:stream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, ['line-length' => 76, 'line-break-chars' => "\r\n"]).
자주 묻는 질문
base64_decode의 두 번째 매개변수는 무슨 역할을 하나요?
base64_decode($s, true)는 strict 모드를 켭니다. 입력에 Base64 알파벳 밖의 문자가 있으면 함수가 false를 반환합니다. 이 인수가 없으면 그런 문자는 조용히 버려집니다.
PHP에 base64url_encode 함수가 있나요?
없습니다. 인코딩에는 rtrim(strtr(base64_encode($data), '+/', '-_'), '=')를, 디코딩에는 base64_decode(strtr($s, '-_', '+/'), true)를 쓰세요. strict 모드는 패딩 누락을 허용합니다.
base64_decode의 strict 모드는 완전한 검사기인가요?
아닙니다. 공백, 패딩 누락, aGl= 같은 0이 아닌 패딩 비트는 여전히 받아들입니다. 정확히 검증하려면 결과를 다시 인코딩해서 정규화한 입력과 비교하세요.
base64_decode가 false 대신 깨진 값을 반환하는 이유는 무엇인가요?
아마 true 없이 호출했을 것입니다. 기본 모드에서 base64_decode('PDw_Pz8-Pg==')는 _와 -를 버리고 잘못된 6바이트를 반환합니다. URL-safe 입력은 먼저 strtr로 변환하세요.