Upload a file
  1. base64hub
  2. Base64 in Code
  3. PHP
Code

Base64 in PHP

PHP has two functions, base64_encode and base64_decode, plus a stream filter for large files. All outputs below come from PHP 8.4.

Encode and decode a string

PHP strings are byte strings, so base64_encode encodes whatever bytes the string holds. A source file saved as UTF-8 gives you UTF-8 Base64 with no conversion step:

<?php
$enc = base64_encode("héllo wörld");
echo $enc;                  // aMOpbGxvIHfDtnJsZA==
echo base64_decode($enc);   // héllo wörld

If text comes from a database or a form in another charset, convert it first with mb_convert_encoding($s, 'UTF-8', 'Windows-1252'). Otherwise the Base64 is correct for those bytes but decodes to mojibake elsewhere. The Base64 to text tool lets you try different charsets on the result.

Strict mode: base64_decode($s, true)

Without the second argument, base64_decode throws away any character that is not in the alphabet and decodes the rest. With true, it returns false instead:

var_dump(base64_decode("aGk=!!"));        // string(2) "hi"
var_dump(base64_decode("aGk=!!", true));  // bool(false)

Always check with ===, because a valid empty input decodes to "", which is falsy:

$bytes = base64_decode($input, true);
if ($bytes === false) {
    throw new InvalidArgumentException('Invalid Base64');
}

Strict mode is less strict than the name suggests. These were tested on PHP 8.4:

InputStrict result
aGk (no padding)"hi"
"aGk=\n" (trailing newline)"hi"
aGl= (non-zero padding bits)"hi"
aGk== (excess padding)false
a (impossible length)false
PDw_Pz8-Pg== (URL-safe chars)false

If you need canonical Base64 only, strip whitespace, decode in strict mode, then check base64_encode($bytes) === $input. The Base64 validator runs the same checks and points at the bad character.

URL-safe Base64 with and without padding

PHP has no built-in Base64URL function. Swap the two characters with strtr, which is faster than str_replace for single-character maps:

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");     // <<???>>

Because strict mode tolerates missing padding, there is no need to add = back before decoding. More on the differences in Base64 vs Base64URL.

Encode and decode a file

echo base64_encode(file_get_contents("hello.txt"));   // aGVsbG8K

file_put_contents("copy.txt", base64_decode("aGVsbG8K", true));

For a data URI, prepend the MIME type: 'data:image/png;base64,' . base64_encode($png). mime_content_type($path) can detect the type if fileinfo is enabled.

Large files with stream filters

file_get_contents loads the whole file and the Base64 copy, so memory use is about 2.3 times the file size. The convert.base64-encode stream filter encodes as it copies:

$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);

Decoding works the same way, or in one line through a php://filter URL:

copy("php://filter/read=convert.base64-decode/resource=big.b64", "big.out");

Both produced output identical to the in-memory functions on a 300 KB test file. A manual loop also works if every chunk is a multiple of 3 bytes, for example fread($in, 3 * 8192). Any other size inserts = in the middle of the output.

MIME line wrapping

Email bodies need lines of at most 76 characters. chunk_split does exactly that, with \r\n line endings by default:

echo chunk_split(base64_encode(str_repeat("x", 60)));
// eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
// eHh4

It also adds a trailing \r\n. Mail libraries do this for you; the MIME Base64 encoder shows the format.

Common pitfalls

  • Forgetting strict mode on untrusted input. Bad data decodes to random bytes and fails somewhere far away from the cause.
  • URL-safe input in default mode. base64_decode("PDw_Pz8-Pg==") returns string(6) "<<���", not <<???>>. Translate with strtr first.
  • Passing null. In PHP 8.1+ you get Deprecated: base64_encode(): Passing null to parameter #1 ($string) of type string is deprecated. Passing an array throws TypeError: base64_encode(): Argument #1 ($string) must be of type string, array given.
  • + turning into a space. Standard Base64 in a query string gets mangled by URL decoding, because + means space there. Use the URL-safe variant or rawurlencode.
  • Treating it as a password store. Base64 hides nothing. Use password_hash for passwords and see encoding vs encryption.

Version notes

  • base64_encode, base64_decode and the $strict flag exist in all supported PHP versions (strict mode was added in 5.2).
  • Since PHP 8.0, base64_decode is declared as returning string|false, and passing an array or object throws TypeError instead of a warning.
  • The convert.base64-encode filter accepts line-length and line-break-chars options for MIME output, for example stream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, ['line-length' => 76, 'line-break-chars' => "\r\n"]).

FAQ

What does the second parameter of base64_decode do?

base64_decode($s, true) enables strict mode: the function returns false when the input contains characters outside the Base64 alphabet. Without it, those characters are silently dropped.

Does PHP have a base64url_encode function?

No. Use rtrim(strtr(base64_encode($data), '+/', '-_'), '=') to encode and base64_decode(strtr($s, '-_', '+/'), true) to decode. Strict mode accepts the missing padding.

Is base64_decode strict mode a full validator?

No. It still accepts whitespace, missing padding and non-zero padding bits such as aGl=. For exact validation, re-encode the result and compare it with the normalized input.

Why does base64_decode return garbage instead of false?

You probably called it without true. In the default mode, base64_decode('PDw_Pz8-Pg==') drops the _ and - and returns six wrong bytes. Translate URL-safe input with strtr first.

Keep reading