文字列のエンコードとデコード
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 からテキストへの変換 ツールでは、結果に対して文字コードを切り替えて試せます。
strict モード:base64_decode($s, true)
2 番目の引数がないと、base64_decode はアルファベットにない文字をすべて捨て、残りをデコードします。true を付けると、代わりに false を返します。
var_dump(base64_decode("aGk=!!")); // string(2) "hi"
var_dump(base64_decode("aGk=!!", true)); // bool(false)
有効な空の入力は falsy な "" にデコードされるので、チェックには必ず === を使ってください。
$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セーフの文字) | false |
正規形の Base64 だけを受け付けたいなら、空白を取り除き、strict モードでデコードしてから、base64_encode($bytes) === $input を確認してください。Base64 バリデーター は同じチェックを行い、不正な文字を指摘します。
パディングあり・なしの URLセーフ Base64
PHP には Base64URL の組み込み関数がありません。2 つの文字を strtr で入れ替えます。1 文字単位の置き換えなら 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 にするには、'data:image/png;base64,' . base64_encode($png) のように MIME タイプを前に付けます。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 を使えば 1 行で済みます。
copy("php://filter/read=convert.base64-decode/resource=big.b64", "big.out");
300 KB のテストファイルでは、どちらもメモリ上の関数と同一の出力になりました。各チャンクが 3 バイトの倍数(たとえば fread($in, 3 * 8192))なら、手書きのループでも動作します。それ以外のサイズでは、出力の途中に = が入ります。
MIME の折り返し
メール本文では 1 行 76 文字以内にする必要があります。chunk_split はまさにそれを行い、デフォルトの行末は \r\n です。
echo chunk_split(base64_encode(str_repeat("x", 60)));
// eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
// eHh4
末尾にも \r\n が付きます。メールライブラリならこれを自動で行います。形式は MIME Base64 エンコーダー で確認できます。
よくある落とし穴
- 信頼できない入力で strict モードを忘れる。 不正なデータがランダムなバイト列にデコードされ、原因から遠く離れた場所で失敗します。
- 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 バージョンにあります(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 の 2 番目の引数は何をするものですか?
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セーフな入力は先に strtr で変換してください。