파일 업로드
  1. base64hub
  2. 코드로 다루는 Base64
  3. PHP
코드

PHP에서 Base64 사용하기

PHP에는 base64_encode와 base64_decode 두 함수와 큰 파일용 스트림 필터가 있습니다. 아래 출력은 모두 PHP 8.4 기준입니다.

문자열 인코딩과 디코딩

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로 변환하세요.

계속 읽기