编码和解码字符串
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
如果文本来自使用其他字符集的数据库或表单,请先用 mb_convert_encoding($s, 'UTF-8', 'Windows-1252') 转换。否则得到的 Base64 对那些字节来说是对的,但在别处解码就会变成乱码。Base64 转文本 工具可以让你对结果尝试不同的字符集。
严格模式: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');
}
严格模式并没有名字听起来那么严格。以下结果在 PHP 8.4 上测试:
| 输入 | 严格模式结果 |
|---|---|
aGk(无填充) | "hi" |
"aGk=\n"(结尾换行符) | "hi" |
aGl=(填充位不为零) | "hi" |
aGk==(填充过多) | false |
a(不可能的长度) | false |
PDw_Pz8-Pg==(URL 安全字符) | false |
如果只接受规范的 Base64,就先去掉空白,用严格模式解码,再检查 base64_encode($bytes) === $input。Base64 校验工具 会执行同样的检查,并指出有问题的字符。
带填充和不带填充的 URL 安全 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"); // <<???>>
由于严格模式能容忍缺失的填充,解码前无需把 = 补回去。两者区别的更多内容见 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 编码器 展示了这种格式。
常见陷阱
- 对不可信输入忘了开启严格模式:错误的数据会被解码成随机字节,然后在离原因很远的地方出错。
- 在默认模式下解码 URL 安全的输入:
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 安全变体或rawurlencode。- 把它当作密码存储方案:Base64 什么都藏不住。密码请用
password_hash,另见 编码与加密的区别。
版本说明
base64_encode、base64_decode和$strict参数在所有受支持的 PHP 版本中都存在(严格模式在 5.2 中加入)。- 从 PHP 8.0 起,
base64_decode的返回类型声明为string|false,传入数组或对象会抛出TypeError,而不再只是警告。 convert.base64-encode过滤器接受line-length和line-break-chars选项来生成 MIME 输出,例如stream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, ['line-length' => 76, 'line-break-chars' => "\r\n"])。
常见问题
base64_decode 的第二个参数有什么作用?
base64_decode($s, true) 开启严格模式:输入中只要有 Base64 字母表之外的字符,函数就返回 false。不开启时,这些字符会被悄悄丢弃。
PHP 有 base64url_encode 函数吗?
没有。编码用 rtrim(strtr(base64_encode($data), '+/', '-_'), '='),解码用 base64_decode(strtr($s, '-_', '+/'), true)。严格模式能接受缺失的填充。
base64_decode 的严格模式算是完整的校验吗?
不算。它仍然接受空白、缺失的填充以及 aGl= 这类不为零的填充位。要精确校验,就把结果重新编码,再与规范化后的输入比较。
为什么 base64_decode 返回乱码而不是 false?
你多半调用时没有传 true。在默认模式下,base64_decode('PDw_Pz8-Pg==') 会丢掉 _ 和 -,返回六个错误的字节。请先用 strtr 转换 URL 安全的输入。