Perl での Base64 エンコード:完全ガイド
あなたの手元には、自分を好きでないチャネルを生き延びさせなければならないデータがあります。JSON フィールドの中に座らなければならないバイナリの塊。HTML タグの中に住まねばならない画像。設定ファイルに属する証明書。URL、ヘッダー、クエリ文字列を旅するトークン。これが Base64 エンコードの日常です:生のデータを 3 バイトごとに、64 文字のアルファベットから 4 文字に書き換え、末尾を 1 つか 2 つの = で仕上げて、結果はどんなものでも運べるプレーンテキストになります。大きさは、出発点よりおおむね 33 パーセント大きくなりがちです。このサイトのホームページでは形式を丸ごと説明しているので、この記事では Perl があなたに何を与えてくれるか、何があなたに黙って決めているか、そして罠がどこにあるかに焦点を当てます。
まずは良い知らせから:encode_base64 は 2002 年から Perl コアの中に住んでおり、C で実装されていて、かなり快適に速いです。面白いのは、この関数には意見があることです。出力を 76 文字でラップし、末尾に改行を付け加え、先にバイトへ変換していない Unicode 文字はエンコードしてくれない:Latin-1 の範囲より上では丸ごと死に、その線より下では黙って Latin-1 バイトだと仮定します。このガイドは、Perl 開発者が実際に生み出す Base64 のすべての姿を巡ります:ワンライナー、MIME ラップされたメール本文、PEM ラップされたキー、URL 安全なトークン、そしてメモリに収まりきらないファイル向けのストリーミング版。
1 つの関数、隠れた改行
API の全体。現代的なドキュメントが提示している、そのままの姿:
encode_base64( $bytes )
encode_base64( $bytes, $eol )
もう一度読んでください。引数は 2 つ、そのうち 1 つは任意、戻り値は 1 つ、フラグなし。任意の第 2 引数は改行シーケンスで、デフォルトは素の改行です。つまり Perl で最も無害に見える呼び出しが、ラップされて改行で終わる出力を生み出すのです:
use MIME::Base64 qw(encode_base64);
my $wrapped = encode_base64("Aladdin:open sesame");
print length($wrapped), "\n"; # 29: 28 文字に改行 1 つ
my $single = encode_base64("Aladdin:open sesame", "");
print length($single), "\n"; # 28:ラップなしには空の文字列を渡す
出力サイズは、呼び出す前に予測できる固定のパターンに従います:
| 入力のバイト数 | 出力の文字数 | パディング |
|---|---|---|
| 0 | 0 | なし |
| 1 | 4 | = が 2 つ |
| 2 | 4 | = が 1 つ |
| 3 | 4 | なし |
| 100,000 | 133,336 | なし、1 行 |
| 3,000,000 | 4,000,000 | なし |
パターンは、3 バイトの完全な組ごとに 4 文字、最後の不完全な組は 1 つか 2 つの = でパディングされる、というもの。知っておく価値のある帰結が 1 つあります:1 バイトも 3 バイトもどちらも 4 文字になるため、エンコード後の長さが正確な入力サイズを隠してしまう。そして仕事をせずにサイズを知りたいなら、このモジュールは 2010 年の 3.10 から長さ関数を持っています。デフォルトではエクスポートされないだけなので、パッケージ名を介して呼びます:
use MIME::Base64 ();
my $with_wrap = MIME::Base64::encoded_base64_length($bytes); # 76 文字行、デフォルトの行末
my $single_line = MIME::Base64::encoded_base64_length($bytes, ""); # ラップなし
my $mime_body = MIME::Base64::encoded_base64_length($bytes, "\r\n");
もう 1 つ、覚えるべきルールがあります。エンコーダーが叫ぶのはこの方法だけだからです:渡す文字列に 255 より大きなコードの文字が含まれていると、encode_base64 は Wide character in subroutine entry で死にます。その線より下では、失敗はもっと静かです:255 までの文字は黙って Latin-1 バイトへダウングレードされるため、変換を飛ばしたアクセント付きの文字列は UTF-8 ではなく Latin-1 としてエンコードされ、誰も教えてくれません。Base64 エンコードは単一バイト文字に対してのみ定義されており、Perl 5.8 以降は文字列に拡張文字を許すので、変換はあなたが意識的に下す判断です。Encode を使って、まさに次のセクションで。
テキストかバイトか:関数ができないステップ
Perl の文字列には、文字を保持しているのかバイトを保持しているのかを示す静かなフラグがあり、Base64 はその線よりバイト側で暮らしています。あなたのテキストが文字列である場合で、JSON パーサー、テンプレート、あるいは UTF-8 ソースファイルにアクセント付きの文字を含まったリテラルから来た瞬間がまさにそれなら - エンコーダーは Latin-1 の範囲を超える文字について、あなたが何を意味したバイトかを推測するのを拒否し、そう言ってくれます。範囲より下では、黙って Latin-1 と推測します。直し方は 2 つの場合で同じで、Encode モジュールのコア関数 1 つです:
use MIME::Base64 qw(encode_base64);
use Encode qw(encode);
my $chars = "H\x{eb}llo W\x{f6}rld"; # 文字列
my $utf8 = encode("UTF-8", $chars); # これで:バイト
my $b64 = encode_base64($utf8, "");
print $b64, "\n"; # SMOrbGxvIFfDtnJsZA==
その encode 呼び出しが全部のダンスです:バイトの表現を選び、現代的なものなら UTF-8、文字をそのバイトに変換し、それから初めてバイトをエンコーダーに渡す。Windows 1252 として到着したレガシーの西方テキストでは、変換は名前が違う同じ関数で、encode("Windows-1252", $legacy) が元の単一バイトの形を手に渡してくれます。Encode モジュールはコアなので、これらはすべて無料です。
さて、罠です。持っているバイトがすでに UTF-8 で、それを「UTF-8 にするつもりで」再び encode("UTF-8", ...) を通すと、コピーは手に入りません:二重エンコーディングが手に入ります。すべてのアクセント付き文字が、それぞれ 2 文字に膨れ上がる、その二重エンコーディングです。典型的な症状は、かつて Hëllo と読めたテキストが今は Hëllo と読めることになり、インターネット上のすべてのデコーダーがそれを忠実にデコードしてくれます:
use Encode qw(encode decode);
my $right = encode("UTF-8", "H\x{eb}llo");
my $wrong = encode("UTF-8", $right); # バイトを文字として再エンコード
print decode("UTF-8", $right), "\n"; # Hëllo
print decode("UTF-8", $wrong), "\n"; # Hëllo
これを防ぐための目安のルール:バイトは正確に 1 回だけエンコードされる、そして utf8::is_utf8() が文字列が線のどちら側にいるかを見せてくれます。フラグが立っていれば、あなたは文字を保持していて、encode() 呼び出しが正しい一手です。立っていなければ、あなたはバイトを保持していて、Base64 の準備ができています。
行ラップ:3 つの方言、1 つのルール
デフォルトの出力はラップされて改行で終わるので、あらゆるエンコード作業での最初の判断は行き先の問題です:この文字列はどこに置くのか。共通の事実は、デコーダーは改行を完全に無視することです:RFC 2045 はデコードするソフトウェアに、すべての改行とアルファベット外の文字を無視するよう指示しているので、ラップは意味論上の違いではなく、行ベースのツールと人間への気遣いにすぎません。実際には 3 つの答えがあります:
改行なし。ラップを無効にした関数出力で、ちょうど 1 行。URL、JSON ペイロード、ヘッダー、データベース値、改行がバグになるあらゆる場所で欲しいのがこれです。また「Base64 が欲しい」と言うときの大半の人の意味もこれです:
my $single = encode_base64($bytes, "");
MIME: 76 文字と CRLF。RFC 2045 のメール慣習:エンコードされた行は 76 文字を超えてはならず、MIME の世界は CRLF で話します。これはモジュール自身の仕事で、第 2 引数で行います:
my $mime_body = encode_base64($bytes, "\r\n");
PEM: 64 文字と LF。キーと証明書は、より短い 64 文字行の古い慣習を使うので、モジュール単独ではその幅を出力できず、4 行のヘルパーが隙間を埋めます:
sub wrap_lines {
my ($text, $width) = @_;
return join "\n", $text =~ /(.{1,$width})/g;
}
my $pem = "-----BEGIN CERTIFICATE-----\n"
. wrap_lines(encode_base64($der, ""), 64) . "\n"
. "-----END CERTIFICATE-----\n";
同じヘルパーは他の 64 文字の方言にも使えます - RFC 7468 の PKIX テキストエンコーディングと、データ行が 64 文字幅で、末尾の CRC24 チェックサム行は GnuPG が付けるものであなたが付けるものではない OpenPGP アーマーです。すべての方言に適用される落とし穴が 1 つあります:encode_base64 は最後の行がちょうどぴったり幅一杯のときでも、結果の末尾に改行を付け加えます。下流のユーザーがその末尾の空白行でつまずくなら、結果への rtrim 呼び出し 1 つで直せます。
URL 安全な Base64: - と _ のアルファベット
標準アルファベットには + と / が含まれ、どちらもテキストファイルの外では厄介です:フォームエンコードされたクエリ文字列の中の + は、あなたのアプリケーションが見る前に空白になりますし、/ は URL でのパス区切り文字です。ファイル名とトークンにはそれぞれ独自の文句があります。RFC 4648 のセクション 5 は、URL 安全かつファイル名安全なアルファベットでこれを解決します:+ が - に、/ が _ に変わり、末尾の = パディングは通常落とされます。RFC はこれが base64 エンコーディングと同じとはみなすべきではない と主張するので、別の形式として扱いましょう。一般的には base64url と呼ばれています。Perl は 2010 年の 3.11 から、これを 1 回の呼び出しで生み出せます:
use MIME::Base64 qw(encode_base64url);
my $seg = encode_base64url("sunset-42");
print $seg, "\n"; # c3Vuc2V0LTQy:パディングなし、改行なし
その 1 回の呼び出しが全部の 3 つの変更をします:アルファベット交換、パディングなし、改行なし。すでに標準 Base64 を持っているのに、行き先が URL 安全な方言を望むなら、2 つの文字列操作でその場で変換できます:
sub to_urlsafe {
my ($b64) = @_;
$b64 =~ tr{+/}{-_};
return $b64 =~ s/=+\z//r;
}
my $seg = to_urlsafe(encode_base64($bytes, ""));
使う場面:JSON Web Token の部品、OAuth の state と nonce パラメータ、URL パスに置く API ID、アドレスバーやファイル名を生き延びる必要がある不透明なキー - こうした用途にちょうど CPAN の Data::UUID::Base64URLSafe があります。使わない場面:メール本文、PEM アーマー、向こう側に標準アルファベットの消費者が座っている場所。なぜなら - と _ は彼らの語彙に入らないからです。そして 2 つのアルファベットを黙って混ぜないでください:URL 安全にエンコードした値は、どこでも、永遠に、URL 安全にデコードされなければなりません。3.11 より前の Perl では、2006 年のスタンドアロンモジュール MIME::Base64::URLSafe、Python の urlsafe コーデックの移植が urlsafe_b64encode を提供します。現代的な Perl なら、組み込みが正しいツールです。
JWT を作る:すべての部品を手作業で
JSON Web Token は現代的な API における Base64 の旗艦消費者で、上のセクションの URL 安全でパディングなしの方言を使います。RFC 7515 によると、コンパクトな JWT はドットで区切られた 3 つの base64url 部品です:保護済みヘッダー、ペイロード、署名。1 つを手作業で作るのは、すべての動く部品を見るのに気持ちの良い方法です:
use MIME::Base64 qw(encode_base64url);
use JSON::PP qw(encode_json);
use Digest::SHA qw(hmac_sha256);
my $secret = "correct-horse-battery-staple";
my $head = encode_base64url(encode_json({ alg => "HS256", typ => "JWT" }));
my $claims = encode_base64url(encode_json({ sub => "homer", role => "admin" }));
my $sig = encode_base64url(hmac_sha256("$head.$claims", $secret));
my $jwt = "$head.$claims.$sig";
print $jwt, "\n"; # コンパクトな HS256 トークン。各 JSON 部品内のキー順は実行ごとに変わる
注目すべき 3 つの詳細があります。第一に、コアの JSON::PP モジュールの encode_json は空白なしのコンパクトな UTF-8 バイトを出力し、それはまさに JOSE の規格がトークンの内部で求めるものです。第二に、ペイロードは誰でも読めるようになっている、それも設計通りです:JWT は署名されたチケットであり、秘密ではないので、機密の値をクレームに入れないでください。第三に、署名は生の HMAC バイトの base64url エンコードです。だからこそ hmac_sha256 は、16 進フォーマットを経ずにそのままエンコーダーに入ります。
本番では、署名を手作業で作りません。CryptX を土台とする CPAN モジュール Crypt::JWT が、アルゴリズムのフルセットで JWS と JWE を実装しています:
use Crypt::JWT qw(encode_jwt);
my $jwt = encode_jwt(
payload => { sub => "homer", role => "admin" },
alg => "HS256",
key => $secret,
);
そして受信側では、accepted_alg でアルゴリズムをピン留めして、攻撃者がトークンを弱いバリエーションに切り替えられないようにしましょう:decode_jwt(token => $jwt, key => $secret, accepted_alg => "HS256") が署名を検証し、失敗すれば croak します。手作業は理解のためなら十分です。本業のためならライブラリで。
HTTP:認証ヘッダー、Data URI、WebSocket ハンドシェイク
Authorization: Basic ヘッダーは最も古い生き残りの使用例です:ユーザー名とパスワードをコロンでつなぎ、1 行にエンコードし、スキーム語で前置きする。空文字列の第 2 引数はここで荷を担っています。ヘッダーフィールドの中の末尾改行はバグだから:
use MIME::Base64 qw(encode_base64);
my $user = "alice";
my $pass = "s3cr3t";
my $header = "Basic " . encode_base64("$user:$pass", "");
print $header, "\n"; # Basic YWxpY2U6czNjcjN0
RFC 2397 の Data URI は、同じ考え方を画像に適用したものです:ペイロードが URL の中に直接座るので、取得のために 2 番目のリクエストは必要ありません。バイナリメディアは ;base64 フラグを使うので、ペイロードはラップを無効にした encode_base64 の出力そのものです:
my $uri = "data:" . $mime_type . ";base64," . encode_base64($bytes, "");
ただ、トレードオフは本物です。エンコードされたペイロードはファイルより約 33 パーセント大きく、HTML ドキュメント自体が大きくなります。ブラウザは Data URI をファイル URL をキャッシュするようにキャッシュしません - キャッシュできる別個のフェッチがないので、ページの表示のたびにバイトがドキュメントの一部としてまた送られ、RFC 自身は Data URI は短い値にしか有用だと述べています。アバター、アイコン、小さなインライングラフィックには使ってください。それ以外は実ファイルを使いましょう。Base64 を静かに使う HTTP の第 3 のコーナーがあります:RFC 6455 の WebSocket ハンドシェイクです。クライアントは Sec-WebSocket-Key ヘッダーを送り、それは 16 バイトの乱数の Base64 です。Mojolicious などのフレームワークが代わりにやってくれますが、ワイヤの上でそれを見かけたら、それが何であるか、もうあなたは知っています:
use MIME::Base64 qw(encode_base64);
my $key = encode_base64(pack("C16", map { int(rand 256) } 1 .. 16), "");
print length($key), "\n"; # 24: 16 バイトの乱数を 4 文字組にパディング
ファイル:全部読み込み、57 バイトチャンク、CLI
最もまっさらなエンコード作業:ファイルがテキストになります。Perl の文字列はバイトなので、探すべきバイナリモードなどありません - :raw レイヤーが全部です。raw で開き、読み、エンコードし、書き:
use MIME::Base64 qw(encode_base64);
open my $in, "<:raw", $ARGV[0] or die $!;
local $/;
my $bytes = <$in>;
close $in;
open my $out, ">:raw", $ARGV[1] or die $!;
print {$out} encode_base64($bytes);
close $out;
:raw レイヤーは重要です。それがなければ、Perl は入出力のどちらかで、バイト列をプラットフォームのテキストとして解釈しようとします。デフォルトのエンコーディングが異なるシステムでは、それはファイルが別の場所で開かれるまで見えない、まさにその破壊です。そしてストレージを計画するときはサイズの見積もりを思い出してください:500 KB の画像は 670 KB のテキストファイルになり、1 GB の動画は 1.33 GB になります。
メモリに収まりきらないファイルには、モジュール自身のドキュメントがルールを与えてくれます:57 バイトの倍数のチャンクでエンコードする、なぜなら 57 バイトのデータがちょうど 1 本の 76 文字行を満たすからです。76 は 57 を 4 で掛け 3 で割ったものです。その境界でチャンクにすれば、ストリームの真ん中にパディングが現れることはありません:
use MIME::Base64 qw(encode_base64);
open my $in, "<:raw", $ARGV[0] or die $!;
while (read($in, my $buf, 57 * 10)) {
print encode_base64($buf);
}
close $in;
各チャンクはちょうど行の境界に座り、最後の、短くなるかもしれないチャンクが最終パディングを保持します。結果は、ファイル全体を一度に読んでエンコードしたものと同じバイト単位で一致します。メモリフットプリントが一定なだけ。そしてスクリプトすら不要なときは、ワンライナーがカバーします:-0777 が入力を一度に全部読み、空文字列の引数が出力を 1 行に保ちます:
perl -MMIME::Base64 -0777 -ne 'print encode_base64($_, "")' < file > file.b64
メール:MIME 本文と添付ファイル
メールこそ、Base64 が名前を稼いだ場所です。MIME の規格は、素のテキストとして安全に乗せないデータは、Content-Transfer-Encoding: base64 で、76 文字を超える行なしに送るべきだと述べています。MIME::Lite でメールを作るなら、全部が 1 つの引数で、モジュールがエンコード、ラップ、ヘッダーをすべてやってくれます:
use MIME::Lite;
my $mime = MIME::Lite->new(
From => 'me@example.com',
To => 'you@example.com',
Subject => 'A file',
Type => 'text/plain',
Data => 'The body text.',
);
$mime->attach(
Type => 'application/octet-stream',
Data => $bytes,
Encoding => 'base64',
Filename => 'hello.txt',
);
Encoding 引数がトリガーです:MIME::Lite は添付ファイルを 76 文字行で Base64 エンコードし(モジュールのデフォルトは素の改行で、それを CRLF に変えるのはメール伝送です)、そのパーツに対応する Content-Transfer-Encoding ヘッダーを刻みます。Email::MIME は同じ立場を取り、あなたが素のデータ文字列として渡すあらゆる添付ファイルを Base64 でエンコードします(そのドキュメント:「この方式で作られたすべてのパーツは、念のため base64 でエンコードされます」)。素の MIME メッセージを手作業で組み立てるなら、等価物はラップのセクションの 2 行、encode_base64($bytes, "\r\n") とヘッダー行 1 本で、それがプロトコル側の物語の全部です。
データベース、設定ファイル、環境変数
データベース:カラムが任意のバイトをそのまま通すことを約束できないため、バイナリデータは多くの場合 TEXT カラムの中で Base64 として乗って運ばれます。1 行形式を保存してください。ラップされたものは絶対にだめ。そうでなければ次の SELECT は、値の真ん中に改行が入った文字列を返します:
use MIME::Base64 qw(encode_base64);
# $dbh はすでに接続済みの DBI ハンドル
my $stmt = $dbh->prepare(q{UPDATE photos SET data = ? WHERE id = ?});
$stmt->execute(encode_base64($bytes, ""), 42);
設定ファイルも同じ形です:バイナリやシークレットのフィールドが 1 行の Base64 文字列である JSON ドキュメント。第 2 引数が存在する理由がまさにここです:
use JSON::PP qw(encode_json);
my $config = {
api_key => encode_base64($key_bytes, ""),
logo_png => encode_base64($png_bytes, ""),
};
open my $fh, ">:raw", "app.json" or die $!;
print {$fh} encode_json($config);
close $fh;
環境変数には 1 つの警告が必要です。小さなトークンを環境変数に載せるのは Base64 で問題ありませんが、エンコード後の形は元より 33 パーセント大きく、オペレーティングシステムは各引数に上限を設けています。Linux では 1 文字列あたり 128 KB の制限で、execve が強制します。環境変数の中の大きな塊は上品に失敗しません:子プロセスは生成された瞬間に不気味なエラーで死にます。小さな値は環境変数に、大きな値はファイルかデータベースに。
パフォーマンス:仕事をしているのは C で、Perl ではない
コアのモジュールは C で実装されており、その C は 1991 年に metamail のために書かれたコードに由来します。これは、含意に気づくまでは楽しい豆知識です:エンコーダーは 30 年のチューニングを持ってきている。現代のマシンでは、ギガバイト毎秒という速さでデータを処理し、それは通常供給しているディスクやネットワークより速いので、Base64 自体がボトルネックになることはほとんどありません。ボトルネックになるのは I/O です。
C コンパイラがない珍しいシステムには、CPAN の純 Perl ツイン MIME::Base64::Perl が同じ基本的なインターフェースを提供します。数倍遅くなりますが、普通のワークロードならまだ快適です。そして 2 つの習慣が大きな仕事を予測可能に保ちます:全部を一度に読むのではなく 57 バイトのチャンクでストリームし、割り当てる前に encoded_base64_length でバッファをサイズ決めする、これなら推測も再割当も省けます。
罠、午後の損失額でランキング
罠たち、およそ噛み付く順番に:
| 罠 | 何が起きるか | 直し方 |
|---|---|---|
| 第 2 引数を忘れる | 出力が 76 文字でラップされ末尾改行付きで到着し、あなたの URL、JSON フィールド、ヘッダーが値の真ん中で壊れる | 1 行の出力には "" を渡す。ラップを期待する行き先にはラップを残す |
| 間違った幅でラップする | PEM の消費者は 64 文字行を期待しているのに 76 が来る、または MIME 本文が 76 文字の上限を超えます | 幅を方言に合わせる:なしなら ""、MIME なら "\r\n"、PEM はヘルパー |
| ワイドキャラクターの croak | 255 を超えるコードを持つ文字列が、リクエストの真ん中で Wide character in subroutine entry で死ぬ | encode_base64 呼び出しの前に、Encode を意図的に名づけて先に文字を通す |
| 二重エンコーディング | すでに UTF-8 のバイトを encode("UTF-8", ...) で再エンコードすると、Hëllo が Hëllo になる |
バイトは正確に 1 回だけエンコード。迷ったら utf8::is_utf8() を確認 |
| アルファベットを黙って混ぜる | - と _ でエンコードした値が標準アルファベットのデコーダーに当たり、ゴミで返ってくる |
値ごとに 1 つの方言を、端から端まで:境界で base64url か標準を選ぶ |
| 末尾の行末 | encode_base64 は最後の行がちょうど一杯のときでも行末を付け加え、厳しい消費者は空白行を見る |
消費者が細ければ、結果に chomp か rtrim をかける |
| データベースの中のラップされた値 | 改行が TEXT カラムの中に着き、次の SELECT は壊れたトークンを返す |
1 行形式を保存。ラップは行き先だけで |
| 環境変数の中の大きな塊 | 33 パーセントの増分で OS の引数ごとの制限が加わり、子プロセスは生成時に不気味なエラーで死ぬ | 小さな値は環境変数に、大きな値はファイルかデータベースに |
| Base64 は保護だと決めつける | この形式は何も隠さず、公開の記録には、ユーザーが IMAP のやり取りを貼り付けてパスワードを誤って明かした実際の事故も文書化されている | 出力は生成された瞬間から機密として扱い、ログの外に出す |
| サイズの見積もりなしで計画する | 500 KB の画像が 670 KB のテキストになり、チェックしなかったストレージやペイロードの制限が噛み付く | コミットする前に、元のサイズの 4/3 を予算付ける |
エンコーダーが語る歴史
Perl の Base64 エンコーダーには 1 分の価値があるキャリアがあり、それは最初の Web ツールキットの中で始まります:
- libwww perl で生まれた。エンコーダーは
LWP::Base64として生まれ、Martijn Koster と Joerg Reichelt が書いて、Gisle Aas が libwww perl の中にMIME::Base64として取り込みました。1997 年 4 月に独自の CPAN ディストリビューションへ卒業、バージョン 2.00 で、チェンジログの項目は単に、libwww perl 5.08 をベースにしていると書かれています。 - 速度の時代。1998 年のバージョン 2.07 が、より速く賢い C によるデコーダーの実装を出しました。当時現代的な Linux マシンで約 25 パーセント速く、チューニングは 10 年間続きます。
- Unicode の時代。2002 年の Perl 5.8 が、255 より大きなコードの文字を普通の文字列の中に入れました。モジュールは段階的に応えました:2001 年の 2.12 はエンコード前に UTF-8 文字列をダウングレードし、現代的な Wide character in subroutine entry の croak は、エンコーダーがその約束を守り続ける方法です。同年のコアとの 2.13 同期は EBCDIC サポートも一緒にもたらしました。Perl での Base64 は今もメインフレームで動いている、という提醒です。
- コマンドラインの時代。2003 年の 2.14 から 2004 年の 3.05 までのリリースは、実際の
encode-base64コマンドを、そのデコードと quoted-printable の双子たちと一緒に同梱していました。2005 年の 3.06 がスクリプトを独立した MIME Base64 Scripts ディストリビューションへ移しました。 - URL 安全の到着。RFC 4648 が 2006 年に URL 安全なアルファベットを規格化し、同じ年にスタンドアロンの
MIME::Base64::URLSafeモジュールが現れ、コアのモジュールは 2010 年の 3.11 でencode_base64urlを 1 回の呼び出しにして追いつきました。 - 現代的なライン。2020 年のバージョン 3.16 がパッケージングを組み直し、最低要件を Perl 5.6 に引き上げました。現在のコアの Perl は 3.16 シリーズを同梱しており、モジュールはコアディストリビューションの中で維持されています。コアモジュールにとって、これほど安全な家はありません。
豆知識、Perl 限定編
この話を良いものにしてくれるトリビア:
- POD の例は魔法のフレーズだ。1997 年から、モジュール自身のドキュメントが
Aladdin:open sesameをエンコードしており、文字列QWxhZGRpbjpvcGVuIHNlc2FtZQ==は、このモジュールの名刺をほぼ 30 年間務めてきました。 - デフォルトの行末は、おそらく期待していないものです。それは素の
\nで、MIME が話す CRLF ではありません。RFC 自身の慣習には第 2 引数が必要で、モジュールにはプロトコルのデフォルトではなく、プログラマーのデフォルトが付いてきます。 - 空の文字列には特別なルールがある。何もエンコードすると、何も返ってこない - 改行も付きません:末尾の行末に関するルールの唯一の文書化された例外で、それが空のファイルがきれいにラウンドトリップする理由です。
- IMAP のいとこにはコンマがある。RFC 3501 のメールボックス名バリエーションは、アルファベットの中で
/をコンマに交換するので、IMAP サーバーから来た Base64 文字列には、標準デコーダーがノイズとして扱う文字が含まれる可能性があります。 - 1991 年の系譜は本物だ。C 実装は、Bellcore の 1991 年のメールプログラム metamail に由来し、それは Perl 5 が生まれる 3 年前のことなので、すべての
encode_base64呼び出しは部分的に 90 年代のコードです。 - 純 Perl のツインには独自の物語がある。2004 年のバージョン 3.00 がコアモジュールから純 Perl の実装を外したとき、チェンジログはそれを、XS 実装の本当の問題を隠す肥大化と呼び、
MIME::Base64::Perlとして再リリースしました。今もそこに暮らしています。
つまり次に、生のバイトがテキストしか通さない世界を旅する必要があるとき、あなたは全部の物語を知っています。関数 1 回の呼び出しが仕事をし、隠れた改行は第 2 引数であなたが決める判断で、ワイドキャラクターの croak はエンコーダーがあなたの Unicode を正直に保つ方法で、URL 安全な方言は 2010 年から 1 回の呼び出しで、ファイルは 57 バイトのチャンクでストリームし、33 パーセントの請求書は入場料です。そしていつか、この旅の逆方向をやる必要がある日 - 英字の文字列を取り、元のバイトを連れ戻す - が来たら、下にリンクする Perl での Base64 デコードの関連記事が、同じ深さでその儀式を扱っています。
最終更新: 2026-10-09