Base64 形式を扱う必要がありますか?それならこのサイトが最適です!データをエンコードまたはデコードするために便利なオンラインツールをご利用ください。

PHP での Base64 エンコード:完全ガイド

あなたが、好まれないチャネルを生き延びなければならないデータを持っています。JSON フィールドに入らなければならないバイナリ blob。HTML タグの中に住まなければならない画像。設定ファイルに収まるべき証明書。URL、ヘッダー、クッキーを渡り歩くトークン。これが Base64 エンコードの日常です:生のデータの 3 バイトを 64 文字のアルファベットから 4 文字に書き換え、末尾を = が 1 つ 2 つで仕上げ、結果はすべてが運べるプレーンテキストになります。このサイトのホームページがこの形式を完全に説明しているので、この記事は PHP があなたに与えるもの、PHP がこっそり決めているもの、そして罠がどこにあるのかに集中します。

PHP 側の物語は、良いニュースから始まります:base64_encode() は PHP 4 からコアにいて、引数は 1 つ、常に文字列を返し、失敗することはない。strict モードも、エラー経路も、設定もない。エンコードは決定的です:同じバイトは常に同じ文字列を生みます。開発者としてのあなたの仕事は、関数を動作させることではなく、周囲の世界を動作させることです:目的地にふさわしいアルファベットを選び、ふさわしい改行を足し、ふさわしい文字コードに変換し、33% のサイズ代を、目を開けて抱えること。(Base64 は通常、データを約 3 分の 1 増やします:入力バイト 3 に対して 4 文字。それは繰り返し出てくるので、頭の片隅に置いておいてください。)

この記事を読み終える頃、PHP 開発者が実際に遭遇する Base64 のすべてのバリエーションを作る方法がわかるようになります:単一行の出力、MIME で折り返されたメール、PEM で折り返されたキー、URL 安全なトークン、data URI、そしてデータが大きすぎてメモリに収まらないときのストリーミングのテクニックです。

1 つの関数、ゼロのオプション

完全な API は、最新の PHP が報告するそのままの形がこちらです:

base64_encode(string $string): string

もう一度読みましょう。1 つのパラメータ、1 つの戻り値、フラグなし。マニュアルはこれを MIME base64 と説明し、「8 ビットクリーンでない転送層、たとえばメール本体などを通してバイナリデータを転送で生き残れるように設計されたもの」と述べています。その言葉が約束していないものに注意してください:改行も、折り返しも、出力がどこに置かれるかについての主張もありません。この関数は 1 つの長い行を出力し、目的地が望む折り返しは、2 回目の呼び出しであなたがやる仕事です。PHP 8.0 から、シグネチャにはネイティブ型が付き、PHP 8.1 から、null を渡すと非推奨の通知が発行されるので、null になり得る値は先に '' に置き換えておいてください。

出力サイズは、呼び出す前に予測できる固定のパターンに従います:

入力バイト 出力文字数 パディング
0 0 なし
1 4 = が 2 つ
2 4 = が 1 つ
3 4 なし
3,000,000 4,000,000 なし
100,000 133,336 = が 2 つ

パターンは、3 バイトの完全なグループごとに 4 文字、加えて 1 つ 2 つの = でパディングされた最後の不完全グループです。知っておくべき結果がひとつ:1 バイトでも 3 バイトでも 4 文字になるので、エンコード後の長さは正確な入力のサイズを隠しています。推定はできます(4 で割り、3 を掛け、パッドを引く)が、正確には読み取れません。

改行はどこに行くのか

base64_encode() は自分で折り返しをしないので、折り返しの判断は目的地の問題です。実際には答えが 3 つあります。

改行なし。関数の生出力、ちょうど 1 行。これが URL、JSON ペイロード、ヘッダー、データベースの値、そして改行がバグになるあらゆる場所で欲しいものです。「Base64 をちょうだい」という多くの人が意味するのもこれです。

MIME 折り返し:76 文字 + CRLF。RFC 2045 の 6.8 節からのメールの慣習です:エンコードされた行は 76 文字を超えてはならず、ディコーダーは改行を無視しなければならない。定番の相棒の呼び出しは chunk_split() で、マニュアルはまさにこの理由から、「See Also」リストで base64_encode() と対で扱っています:

$binary = file_get_contents('/var/www/uploads/report.pdf');
$wrapped = chunk_split(base64_encode($binary), 76, "\r\n");

PEM 折り返し:64 文字 + LF。キーと証明書は、より古い Privacy-Enhanced Mail(RFC 1421)の慣習を使います:短い 64 文字行。同じツール、数字が違うだけです:

$der = file_get_contents('/etc/ssl/raw-key.der');
$armor = "-----BEGIN PRIVATE KEY-----\n"
  . chunk_split(base64_encode($der), 64, "\n")
  . "-----END PRIVATE KEY-----\n";

chunk_split() の落とし穴が、2 つの折り返しバリエーションに共通します:この関数は、入力の長さが行長のちょうど倍数であっても、結果の末尾に区切り文字を追加します。下流のコンシューマーが末尾の空行でつまずくなら、これが理由です。区切り文字への rtrim() 呼び出しで直ります。いつかあなたを助ける非対称性にも注意してください:ディコーダーは改行を完全に無視するので、MIME で折り返されたペイロードも折り返さないものも、同じバイトにデコードされます。折り返しは、行ベースのツールと人間への気配りで、意味の違いではないのです。

出力を URL 安全にする

標準アルファベットには + と / が含まれ、どちらもテキストファイルの外では厄介です。フォームエンコードされたクエリ文字列の + は、アプリケーションがそれを見る前にスペースになってしまいますし、/ は URL 中のパスの区切り文字です。ファイル名やトークンにはそれぞれの文句があります。RFC 4648 の 5 節は、URL とファイル名で安全なアルファベットでこれに答えます:+ が - に、/ が _ になり、末尾の = パディングは通常落とされます。RFC はこれを「base64 エンコードと同じとは見なすべきではない」と主張するので、別の形式として扱いましょう。一般的には base64url と呼ばれます。

これを作るのは文字列操作 2 つです:

function base64url_encode(string $data): string
{
  return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
var_dump(base64url_encode("hi>?there\x00bin")); // string(18) "aGk-P3RoZXJlAGJpbg"

使う場面:JSON Web Token の各部分、OAuth の state と nonce パラメータ、URL パスに置く API ID、アドレスバーやファイル名にコピーされるもの全般。使わない場面:メール本体、PEM アーマー、そして向こう側に標準アルファベットのコンシューマーがいる場所。- と _ は相手の語彙に含まれないからです。そして 2 つのアルファベットを黙って混ぜないでください:URL 安全にエンコードされたトークンは、どこでも、永遠に、URL 安全にデコードされなければなりません。それが base64url の相互運用性のルールすべてです。

Unicode と、あなたが意図したバイト

PHP の文字列はバイト列で、base64_encode() は、与えられたバイトが何を意味するのか聞かずにエンコードします。それは、あなたの「テキスト」が実はあなたが思っている文字コードではない日が来るまで、特徴です。古典的な失敗:エディタでは UTF-8 に見えて、レガシーソースから Windows-1252 として届いた文字列。そのバイトをそのままエンコードすると、デコードして UTF-8 を前提する受信側は、アクセント付きの文字の代わりにもじばけを受けます。

修正は、エンコードする前に正規化することです。mbstring 拡張機能を使って(PHP ソースには同梱されていますが、デフォルトでは無効):

$fromLegacy = "caf\xE9 au lait"; // Windows-1252 のバイト:é は 0xE9
$utf8 = mb_convert_encoding($fromLegacy, 'UTF-8', 'Windows-1252');
$encoded = base64_encode($utf8);
var_dump($utf8); // string(13) "café au lait":é は今や 2 バイトの UTF-8

ソースがすでに UTF-8 なら、変換をスキップできます。手頃な健全性チェックは mb_check_encoding($utf8, 'UTF-8') です。助言を 1 文:テキストとして再エンコードして、すでにエンコードされた Base64 文字列を「直そう」としないでください。それは下記の落とし穴セクションの二重エンコードの罠で、PHP コードベースで最も多い Base64 バグの 1 つです。

ファイル、blob、.b64 の慣習

最もまっさらなエンコード作業:ファイルがテキストになります。PHP の文字列はバイトなので、気にする「バイナリモード」はありません;file_get_contents() が正確なバイトを手渡し、base64_encode() が正確なテキストを手渡します:

$binary = file_get_contents('/var/www/uploads/photo.png');
$encoded = base64_encode($binary);
file_put_contents('/var/www/uploads/photo.png.b64', $encoded);
var_dump(strlen($binary), strlen($encoded)); // 毎回、33% の代償

この作業を安全にする 2 つの習慣。まず、何をエンコードしているかを知ること。finfo クラス(fileinfo 拡張機能、標準の PHP ビルドに同梱)は、ファイル名ではなくバイトから実際のタイプを教えてくれます:

$mime = (new finfo(FILEINFO_MIME_TYPE))->file('/var/www/uploads/photo.png');
var_dump($mime); // string(9) "image/png"

第二に、ストレージを計画するときはサイズ代を思い出してください:500 KB の画像は 670 KB のテキストファイルに、1 GB の動画は 1.33 GB のテキストファイルになります。それが下記の大数据セクションが存在する理由です。

data URI:ページに画像を埋め込む

data URI はペイロードを URL に直接埋め込むので、取得のための 2 回目のリクエストは不要です。RFC 2397 は形を定めています:data:、任意のメディアタイプ、任意の ;base64 フラグ、カンマ、そしてデータ。画像のようなバイナリメディアではフラグが存在するので、ペイロードはまさに base64_encode() が生んだものです:

function data_uri_for(string $path): string
{
  $mime = (new finfo(FILEINFO_MIME_TYPE))->file($path);
  $payload = base64_encode(file_get_contents($path));
  return 'data:' . $mime . ';base64,' . $payload;
}
$uri = data_uri_for('/var/www/uploads/avatar.png');
echo '<img src="' . $uri . '" alt="avatar" />' . "\n";

ここでなぜ Base64 なのか?URI は生のバイトやカンマを安全に含められず、Base64 のアルファベットはエスケープをまったく必要としないからです。ただしトレードオフは実在します。エンコードされたペイロードはファイルより約 33% 大きく、HTML ドキュメント自体が大きくなります。ブラウザはファイル URL をキャッシュするように data URI をキャッシュしないので、すべてのページ表示でバイトを再ダウンロードします。そして RFC 自身は、data URI は短い値にしか役に立たないと述べています。古い HTML パーサーは属性長に厳しい制限があり、最新のブラウザははるかに寛容ですが、タグの中のメガバイトは依然として好まれません。アバター、アイコン、小さなインライングラフィックスに使ってください;それ以外は実際のファイルを使ってください。

JWT と API トークン

JSON Web Token はモダンな API における Base64 の旗艦の消費者で、上記のセクションの URL 安全、パディングなしの方言を使います。RFC 7519 に従い、コンパクトな JWT はドットで区切られた 3 つの base64url 部分です:ヘッダー、ペイロード、署名。ヘッダーとペイロードはプレーンな JSON;署名は生のバイトです。手作業で作ることは、すべての動く部分を見る快い方法です:

function base64url_encode(string $data): string
{
  return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
$header = base64url_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
$payload = base64url_encode(json_encode([
  'sub' => '1234567890',
  'name' => 'John Doe',
  'iat' => 1516239022,
]));
$signingInput = $header . '.' . $payload;
$signature = base64url_encode(hash_hmac('sha256', $signingInput, $secret, true));
$token = $signingInput . '.' . $signature;

注目すべき点が 2 つ。署名は生の HMAC バイトの base64url エンコードです。それが hash_hmac() が生の出力のために true を引数に呼び出される理由です。そしてヘッダーとペイロードは誰でも読めますが、それは設計どおりです:JWT は署名されたチケットであり、秘密ではない。本番では、署名も検証も手作業で書かないでください。コミュニティのパッケージは firebase/php-jwt(v7、PHP 8.0 以上を必要とします)で、Composer でインストールします:

composer require firebase/php-jwt
use Firebase\JWT\JWT;
$secret = 'correct-horse-battery-staple-long-enough-secret';
$token = JWT::encode([
  'sub' => '1234567890',
  'name' => 'John Doe',
  'exp' => time() + 3600,
], $secret, 'HS256');
var_dump(substr_count($token, '.')); // int(2):ヘッダー、ペイロード、署名

ライブラリの v7 系に関するバージョン注意:HMAC アルゴリズムは最小キー長を強制するので、32 バイト未満の HS256 シークレットは、エンコードが始まる前に拒否されます。長いシークレットはもともと常道です。これは単に、ライブラリがそれを大雑把に扱わないようにするだけです。

ライブラリは base64url 変換、署名、期限チェックをあなたのために処理し、半信半疑のデータを返す代わりに型付きの例外を投げます。これを使ってトークンを書くとき、base64_encode() に直接触れることはなく、それはまさにあるべき姿です。

HTTP: Basic 認証と WebSocket ハンドシェイク

PHP が Base64 をやります、プロトコルが残りをする、というヘッダー構築の作業が 2 つ。

HTTP Basic 認証(RFC 7617):クライアントは Authorization: Basic と、username:password の Base64 を送ります。これを組み立てるのは文字列連結 1 つです:

function basic_authorization_header(string $username, string $password): string
{
  return 'Basic ' . base64_encode($username . ':' . $password);
}
$headers[] = 'Authorization: ' . basic_authorization_header('alice', 'secret123');

声に出して 1 回言ってください。RFC がそう要求しているからです:これは保護ではなくエンコードです。パケットキャプチャを持つ誰でも、一連打で両方を復元できるので、Basic 認証は HTTPS 接続に属するものです。

WebSocket ハンドシェイク(RFC 6455):サーバーは、変換されたキーをエコーすることで、クライアントを聴いたことを証明します。クライアントの Sec-WebSocket-Key と固定のマジック GUID を連結し、その結果の SHA-1 を取り、ダイジェストを base64 エンコードします。これは標準 Base64 で、パディング込みです。URL ではなくヘッダーに暮らしているからです:

function websocket_accept_key(string $clientKey): string
{
  return base64_encode(hash('sha1', $clientKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
}
$accept = websocket_accept_key('dGhlIHNhbXBsZSBub25jZQ==');
var_dump($accept); // string(28) "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="

その例は RFC 自身のものです。それが便利な自己テストになります:あなたの実装が同じ 28 文字を生むなら、WebSocket レイヤーは正しく話しています。

メール:元のユースケース

この記事の其余のすべては、一つの事実の子孫です:SMTP は 7 ビット ASCII を運ぶように設計され、人々はバイナリを送りたかった。MIME 標準の答え、RFC 2045 の 6.8 節では、Base64 が Content-Transfer-Encoding となり、すでに出会った 2 つのハウスルールが付きます:最大 76 文字の行、そしてアルファベット外のすべての文字を無視するディコーダー。だから PDF の添付ファイルはこうして旅します:

$pdf = file_get_contents('/var/www/uploads/report.pdf');
$attachment = chunk_split(base64_encode($pdf), 76, "\r\n");
// メールライブラリは $attachment を MIME 部分に置く
// Content-Transfer-Encoding: base64 を付けて

実用的な数字:エンコード自体は 33% のコスト、76 文字ごとの CRLF はもう少しコストが増し、だから 100 KB の添付ファイルは約 137 KB のテキストとして出荷されます。PHP からメールを書くとき、ライブラリ(PHPMailer とその安定的な親戚たち)が折り返しをあなたのためにやってくれ、あなたは生のバイナリを手渡します。もし素の .eml ファイルで、76 文字幅の文字の壁を見かけることがあるなら、それが生んだ正確なアルゴリズムを今あなたは知っています。

キーと証明書の PEM アーマー

キーと証明書は、文字の壁より多いものが必要です:ラベルです。PEM アーマーは BEGIN 行、64 文字で折り返された Base64 ブロック、END 行から成り、Privacy-Enhanced Mail(RFC 1421)から受け継がれ、OpenSSL に守られてきた慣習です。PHP の openssl 拡張機能は、この形を直接生成・消費します:

$res = openssl_pkey_new([
  'private_key_bits' => 2048,
  'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
openssl_pkey_export($res, $pem);
// $pem はすでにアーマー済み:BEGIN ラベル、64 文字行、END ラベル

興味深いのは、アーマーを手作業で組み直す場合です。たとえば、API から生の DER バイトを受け取り、PEM しか読まないツールのために PEM ファイルが必要になったとき。慣習は、1 行 64 文字、LF の改行、そして中身を示すラベルです:

$rearmored = "-----BEGIN CERTIFICATE-----\n"
  . chunk_split(base64_encode($der), 64, "\n")
  . "-----END CERTIFICATE-----\n";
var_dump(openssl_x509_parse($rearmored) !== false); // bool(true):アーマーは有効

ラベルを間違えたら、Base64 が完璧でもファイルはゴミになります。そして行長を間違えたら、ディコーダーは改行を無視するのでほとんどのツールは読めますが、diff ツールと人間が苦しみます。64 がその数字です。

設定ファイル、環境変数、データベース

Base64 はテキストの容器であり、それにより、さもなくば自分の容器を壊してしまう値の密輸ツールになります。セミコロンと引用符でいっぱいのデータベース DSN、.env ファイルの中の JWT、TEXT 列の中のバイナリ blob:すべてが 1 つの長い安全な文字列になります。

環境変数のバリエーションは、2 ステップの儀式です。一度だけ、設定を構築するマシンで、エンコードします:

echo 'DB_DSN_B64=' . base64_encode('pg:host=db;password=qu"ote') . PHP_EOL;

そして、アプリケーションのすべての起動で、起動時にデコードして検証します。そうすると、半分貼り付けられた設定は、暗号的ではなくはっきりと失敗します:

$dsn = base64_decode(getenv('DB_DSN_B64') ?: '', true);
if ($dsn === false) {
  exit('DB_DSN_B64 is not valid Base64.');
}

データベースでは、同じアイデアでバイナリをテキスト列に保存します。サイズ代が適用されます:保存される値は blob より約 33% 大きいので、1 MB のファイルは列の中で約 1.33 MB を占め、列のタイプはそれを念頭に選んでください。そしてどこでも同じ警告:これは形式の安全性であって、秘密性ではありません。設定を読める、または列をクエリできる人は、1 回の呼び出しで逆変換できます。値が機密なら、暗号化してください;Base64 が実現するのは可搬性だけです。

大きなデータと安定したメモリ

エンコードはあなたにコストをかける方向です:出力は入力より 3 分の 1 大きくなるので、2 GB のバイナリはメモリに 2.66 GB のエンコード済み文字列を求めます。長生きする Web プロセスや、メモリが制限されたホストでは、まとめて読むのではなくストリームする理由になります。PHP は 2 つの方法を与えてくれます。

最初の方法は convert.base64-encode ストリームフィルタで、この関数のストリーミング版の双子です。連想配列としてパラメータをサポートします:折り返し幅の line-length と区切り文字の line-break-chars。これにより、文字列全体を抱えずに chunk_split() の効果を再現できます:

$in = fopen('/var/www/uploads/big.bin', 'rb');
$out = fopen('/var/www/uploads/big.b64', 'wb');
stream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, [
  'line-length' => 64,
  'line-break-chars' => "\n",
]);
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);

二つ目の方法は、古典的な 57 バイトの技巧で、PHP の小さな伝承です。76 文字の MIME 行はちょうど 57 バイトの元データを保持するので、入力ファイルを 57 バイトの倍数のチャンクで読むと、各チャンクは独立してエンコードされ、チャンク間で運ぶ残りビットはありません。8151 バイトのチャンク(57 × 143:出力 76 文字行が 143 本、PHP の伝統的な 8192 バイト I/O バッファに近い)で読むと、メモリは平らのまま、ファイルは MIME 完璧な形でストリームされます:

$in = fopen('/var/www/uploads/big.bin', 'rb');
$out = fopen('/var/www/uploads/big.b64', 'wb');
while (!feof($in)) {
  $plain = fread($in, 57 * 143);
  $encoded = chunk_split(base64_encode($plain), 76, "\r\n");
  fwrite($out, $encoded);
}
fclose($in);
fclose($out);

どちらを選びますか?配管を PHP に任せたい、正確なチャンク境界を気にしないならフィルタです。決定的な MIME 出力、進捗フック、バッファサイズの厳密な上限が欲しいなら 57 バイトのループです。どちらにせよ、メモリフットプリントは 1 つのファイルではなく、1 つのチャンクで留まります。

PHP アクセントの落とし穴

実際の PHP コードベースで現れる罠を、ひとまとめに:

  • 二重エンコード。古典的です:すでに Base64 な値(環境変数、データベース、前のスクリプトから)が、誰も確認しなかったために base64_encode() にまた通されます。結果は 1 回デコードすると、さらに Base64 が返ってきます。治し方は、境界での往復チェック、またはコードベースのすべてのエンコードを所有する 1 つの周知の関数です。
  • URL 中の +。標準出力には + と / が含まれます。フォームエンコードされたクエリ文字列では、プラスはあなたのコードがそれを見る前にスペースになります;パスでは区切り文字です。URL に紐づくものなら、base64url を出力するか、rawurlencode() で値全体をパーセントエンコードします。
  • 末尾の区切り文字。chunk_split() は、行長のちょうど倍数でも結果を区切り文字で終わらせます。末尾の空行は通常無害ですが(ディコーダーは無視します)、素朴な行数カウンタや diff ツールを引っ掛けます。コンシューマーが気難しいなら、区切り文字を rtrim() してください。
  • 折り返しの不一致。76 文字の MIME 行で書き、コンシューマーが 64 文字の PEM 行を期待する(またはその逆)ことは、ディコーダーが改行を無視するのでデコードの問題ではありませんが、行ツールや人間の読みやすさの問題です。目的地が期待する慣習を選んで、それに従いましょう。
  • 末尾の改行はデータです。base64_encode() は、テキストファイル末尾の改行も含め、すべてのバイトをエンコードします。2 つのシステムが同じように見えるテキストに対して「異なる」Base64 を生むとき、末尾の \n がいつもの疑わしい相手です。
  • Base64 は暗号化ではありません。パスワードをデータベースに届かせる前にエンコードしても、それは保護されません;形式を整えられただけです。「暗号化された」列は、クエリアクセスを持つ人にとって 1 つの関数呼び出し分のプレーンテキストです。本当に機密なものは暗号化またはハッシュしてください;Base64 は伝送のコスチュームです。
  • メモリは 3 分の 1 大きくなります。32 ビットの PHP ビルドや、メモリ制限が厳しいホストでは、大きなバイナリのエンコードは丸ごと失敗し得ます。memory_limit を調整する前に、上記のようにストリームしてください。
  • 改行は決して追加されません。関数の説明にある「MIME base64」は、「MIME で折り返された出力」を意味しません。出力に 76 文字行が必要なら、chunk_split() かフィルタで追加します。

base64_encode の短い歴史

PHP の物語のエンコード側は、最善の意味で、ほぼ爽快なほど退屈です。base64_encode() は PHP 4 で、1 つのパラメータ、オプションなしのコア関数として登場し、それ以来 1 つも増やしていません。strict モードは決して必要ありませんでした(データを生み出しているのはあなた自身なので、strict にすべきものが何もない)、パディングオプションは決して追加されず、折り返しは初日から chunk_split() に委ねられました。それが 2 つの関数が今もマニュアルの「See Also」リストで一緒に座っている理由です。

マニュアルは、誰も覚えている限りずっと、33% という数字を載せてきました:「Base64 でエンコードされたデータは、元のデータより約 33% 多くのスペースを消費します」。その文は今日もそこにあり、それがこの記事にこの数字が現れる理由です。ストリームフィルタ convert.base64-encode は後に加わり、自分の成長のために脱ぎ捨てねばならなかったバグを持っていました:2015 年、PHP は欠陥(バグ #68532)を修正しました。フィルタはメモリストリームでの読み取りモードで、最終パディング文字を省略し得るものでした。これはまさに、関数ベースの経路が決して被らない種類の静かな破損です。PHP 8.0 がネイティブの string パラメータと戻り型を追加し、そこで変更履歴は終わります。1 つの関数、1 つのパラメータ、20 年、ゼロのオプション:最初の一回で表面を正しく作ることの記念碑です。

楽しい PHP の事実

奇妙さがあれば参照は完全ではないので:

  • 空の恒等性。base64_encode('') は '' です。パディングも、出力も、驚きもなし:空が入り、空が出る。
  • 奇妙な住所。PHP マニュアルは base64_encode() を「その他の基本拡張機能」という本の「URLs」チャプターに分類しています。「エンコード」チャプターはありません。「URLs」チャプターで、parse_url() と一緒に見つけることができます。
  • アルファベットは一度も動いていません。同じ 64 文字が、PHP 4 以来 PHP のエンコーダーから出てきました。2001 年の Windows マシンでの PHP 4 スクリプトが生んだ Base64 文字列は、今日の Linux 上の PHP 8.4 で同じようにデコードされます。それが 25 年の実績を持つ相互運用性です。
  • 1 バイトと 3 バイトは、長さが同じに見えます。どちらも 4 文字を生み、パディングだけが両者を区別します。この記事のサイズ表が存在する理由です。
  • 57 はマジックナンバーです。76 文字の MIME 行はちょうど 57 バイトの元データを保持し、その偶然が、大数据セクションのストリーミングチャンクループを、読み取りの間に状態を持たずに可能にしています。
  • ダイヤルアップ時代の兄弟がいます。base64_encode() の「See Also」は、convert_uuencode() すらリストしています。これは uuencode の PHP ラッパーで、MIME が Base64 を標準化する前にメールのためにバイナリをエンコードしていた形式です。String Functions(文字列関数)チャプターにいますが、この関数の本来の目的の化石記録です。
  • 古いブラウザはパディングに厳しかったです。2004 年の php.net のノートは、Internet Explorer が = を含むクッキー名を拒否したと報告しています。だからベテランのコードは、クッキーに保存された Base64 の末尾パッドをときどき落とします。モダンな環境ではそのトリックは不要ですが、あなたが引き継ぐかもしれない奇妙な rtrim($x, '=') 呼び出しを説明してくれます。

その裏側

これがエンコード側で、2 つのうち簡単なほうです:関数は失敗せず、出力は決定的で、形式はエンドツーエンドであなたが制御するものです。難しい方向は、他の人々の Base64 を受け取るほうです:彼らのパディングの選択、改行、URL 安全な方言、壊れた貼り付け。そこが、ディコーダーが strict モード、検証パイプライン、そしてすべてへの健全な疑いを必要とする場所です。このページからリンクされる「PHP での Base64 デコード」が、デコード側を同じ深さで扱います。

最終更新: 2026-10-09

関連記事: PHP での Base64 デコード:完全ガイド