Ruby での Base64 エンコード:完全ガイド
あなたのデータには、今の姿を許さない行き先があります。JSON ドキュメントの中に住まなければならない画像。URL を越えて行かなければならないトークン。7 ビットテキストのために設計されたプロトコルを生き延びなければならない添付ファイル。クォーティングを壊さずに環境変数の中に座らなければならない秘密。それぞれの場所では、ここと向こうのあいだの何かが、あなたのバイナリを破壊しようとしています - そしてその対策には名前があります:Base64。
Ruby では、この仕事の全部が、言語と一緒に出荷される 1 つのモジュールに住んでいます。エンコーダーは 3 つ、インストールは不要で、コードを実行する前に、出力を正確な 1 文字単位まで予測できます。その予測可能性こそが、多くのガイドが飛ばす物語の半分です。エンコードこそが、サプライズが料金を請求する場所だからです:末尾の改行が JSON に忍び込み、誰も求めなかった改行がトークンを分割し、アルファベットの選択を 1 つ間違えれば URL が台無しになります。このガイドは、3 つのエンコーダー全部、出力の計算、そして Ruby 開発者が実際にエンコードするあらゆるペイロードを通し、サプライズがサプライズでなくなりますように。
始めに、復習を 1 つ:Base64 はデータを 3 バイトずつ書き換え、64 文字のアルファベットから 4 文字を出力し、入力が 3 で割り切れないときは、1 つか 2 つの = をパディングとして付けます - それが、出力が入力よりおおむね 3 分の 1 大きくなる理由でもあります。このサイトのホームページではこの形式を十分に扱っているので、この記事は形式の話を 1 息にまとめたまま、すぐ仕事に取りかかります。
どのエンコーダーが必要か?
Ruby は 3 つのエンコーダーを与えてくれます。その選択は 3 問のクイズです:出力に改行を含めていいのか?+ や / を含めていいのか?パディングを含めていいのか?全部のラインナップがこちら:
| エンコーダー | 出力の形 | 改行 | パディング | 手を出す場面 |
|---|---|---|---|---|
Base64.strict_encode64(bin) |
1 行、標準アルファベット | なし | 常に存在 | JSON、トークン、API、ファイル - 安全なデフォルト |
Base64.encode64(bin) |
複数行、標準アルファベット | 60 文字ごと、そして末尾に 1 つ | 常に存在 | メール本文など、行指向のテキストプロトコル |
Base64.urlsafe_encode64(bin, padding: true) |
1 行、ハイフン・アンダースコアのアルファベット | なし | あなた次第、デフォルトでオン | URL、cookie、識別子に入るものすべて |
時間に追われて決めるなら、短い答えはこうです:デフォルトは strict_encode64、結果が URL の中を旅するなら urlsafe_encode64、受信側が短い行を望むテキストプロトコルなら encode64。以下すべてが、なぜそうなのか、そして各選択がどこで静かにあなたにコストを払わせるのかを説明します。
strict_encode64:主力
Base64.strict_encode64 は、あなたのコードの大部分で実際に使うエンコーダーです。ちょうど 1 行の出力を、常に正しいパディング付きで、標準アルファベットから作り出します:
require "base64"
Base64.strict_encode64("hello world")
# => "aGVsbG8gd29ybGQ="
Base64.strict_encode64("s")
# => "cw=="
そしてこのアルゴリズムは決定論的なので、出力の正確な長さを入力の長さから予測できます - 推測もなし、データベースの列で 1 つずれるバグもなし。下記の表が全部の計算です:
| 入力の長さ | 出力の長さ | 末尾のパディング |
|---|---|---|
| 3n バイト(割り切れる) | 4n 文字 | なし |
| 3n + 1 バイト | 4n + 4 文字 | = が 2 つ |
| 3n + 2 バイト | 4n + 4 文字 | = が 1 つ |
つまり 11 バイトは 16 文字になり、100 バイトは 136、1 メガバイトのファイルは約 1.33 メガバイトのテキストになります。その 3 分の 1 の増加は、あなたが運ぶすべての Base64 ペイロードの入口料であり、列もキャッシュも API のレート制限も息苦しく感じ始めたら、ポケットに入れておきたい数字です。
Base64.strict_encode64("123")
# => "MTIz" 入力は 3 バイト、出力は 4 文字
Base64.strict_encode64("1234")
# => "MTIzNA==" 入力は 4 バイト、出力は 8 文字、パッド 2 文字
Base64.strict_encode64("12345")
# => "MTIzNDU=" 入力は 5 バイト、出力は 8 文字、パッド 1 文字
encode64:改行を足すやつ
Base64.encode64 は古典です。そして、1 つの午後以上を終焉に導いてきた振る舞いが 1 つあります:出力を折り返します。60 文字ごとに新しい行を始め、常に末尾の改行で終わります:
Base64.encode64("hello world")
# => "aGVsbG8gd29ybGQ=\n"
Base64.encode64("*" * 46)
# => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n"
この折り返しはバグではありません - これは MIME の世界というこのメソッドの元の出身地から受け継いだ特徴で、そこでは長い行がプロトコル違反だったからです。Ruby の mail gem はそれを意図的に頼りにしています - その Base64 エンコーダーには、Ruby の行折り返しが出力を SMTP の行長制限の内に保つ、という趣旨のコメントまで入っています。メール本文をエンコードしているなら、encode64 はあなたに恩を売ってくれているのです。
しかしその他のすべての文脈では、折り返しは税金です。最もよくある事故は、Base64 の値が突然 2 行にまたがる JSON ドキュメントです:
payload = { "logo" => Base64.encode64(File.binread("logo.png")) }
puts payload.to_json
# logo の値には、誰も求めなかった改行が乗っている
そして同じバグの小さな双子が、短い文字列の末尾改行です:Base64.encode64("s") は "cw==\n" を返すので、URL に貼り付けたトークンや、期待値との比較が、20 分追いかけたくなる理由で失敗します。治療法は strip です - でももっと良い治療法は strict_encode64 で、それはあなたが稼がなかった 1 文字すら足しません。なお、知る価値のある魅力的な非対称性が 1 つあります:空の入力は末尾の改行なしの空の文字列を生成するので、Base64.encode64("") はただ "" なのです。
urlsafe_encode64:リンク安全なアルファベット
標準アルファベットのうち 2 文字が、URL パーサーが眺めているあらゆる場所で厄介をまき散らします:+(クエリ文字列ではスペース)と /(パスの区切り)。RFC 4648 はこれを入れ替えで解決しました - - が + の代わりに、_ が / の代わりに - そして Ruby はそれを Base64.urlsafe_encode64 で実装しています:
Base64.urlsafe_encode64("\xfb\xef\xbe".b)
# => "----"
Base64.urlsafe_encode64("\xff\xff\xff".b)
# => "____"
この 2 つの例が、アルファベットの見本市です:標準エンコーダーが ++++ や //// と描く同じバイトが、---- と ____ として出てくる - それはパーセントエンコーディングを一切なしに、URL、パス、ファイル名、フォームフィールドのすべてを生き延びる文字です。出力は 1 行で、strict_encode64 と同じです。
このメソッドの唯一のオプションは padding: キーワードで、Ruby 2.3 で追加されました。知るべきはこれです。JSON Web Token 仕様はパディング なし の base64url を要求し、他の多くのトークン方式も同様です:
Base64.urlsafe_encode64("*")
# => "Kg=="
Base64.urlsafe_encode64("*", padding: false)
# => "Kg"
パディングをオフにすると、長さの計算が動きます:3n + 1 バイトは 4n + 2 文字になり、3n + 2 バイトは 4n + 3 文字になります。デコード側は対処できます - Ruby の urlsafe_decode64 は足りないパディングを自分で足す - だからパディングなしの出力を出すのは安全ですが、相手側が厳格な RFC 2045 の読み手なら、パディング付きの出力がより友好的なデフォルトです。1 つの注意点:パディングをオフにするのは、仕様がそれを要求するときだけにしてください。1 つか 2 つの文字が節約でき、代わりにデコーダーからの文句の 1 類を買ってきます。
Ruby が実際にエンコードするのは:文字列はバイト
ユースケースの前に、すべてを形づくる Ruby 特有の事実を 1 つ:Ruby の文字列は、文字コードのタグを着たバイトの並びで、エンコーダーが見るのはバイトだけです。タグは Ruby に文字列をどう表示し、どう比較するかを伝え、エンコードされるものを変えるわけではありません:
require "base64"
s = "h\u{e9}llo"
puts s.encoding
# => UTF-8
puts s.bytes.length
# => 6 アクサンつきの e は 2 バイト
Base64.strict_encode64(s)
# => "aMOpbGxv"
これが「出力が期待より長いのはなぜか」という罠の裏側です:あなたが打った文字列は、通常、文字数よりバイト数の方が長くて、Base64 はバイトあたりで料金を取ります。逆方向も同じくらい静かです - 無効な UTF-8 文字列でも、何も文句を言わずにエンコードされます。エンコーダーに検証するものがないからです:
broken = "h\u{e9}llo".b.force_encoding("UTF-8")
broken.setbyte(1, 0xFF)
puts broken.valid_encoding?
# => false
Base64.strict_encode64(broken)
# => ある base64、エラーなし、バイトはバイト
本物のバイナリなら、テキストの機構を全部スキップして、pack でバイトを組み立てるか、File.binread で読みましょう。満足感のある例は PNG シグネチャです - この地球上のすべての PNG ファイルを開く 8 バイト:
png_magic = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A].pack("C*")
Base64.strict_encode64(png_magic)
# => "iVBORw0KGgo="
JWT:読めるデータを署名する
JSON Web Token は、Ruby の URL 安全エンコーダーの最大級の利用者です。トークンはドットで結ばれた 3 つの base64url セグメント - ヘッダー、ペイロード、署名 - で、仕様は明確です:アルファベットは URL 安全なもの、パディングはオフ。すべてを jwt gem が処理します:
# Gemfile に:gem "jwt"
require "jwt"
token = JWT.encode(
{ sub: "1234567890", name: "Alice", exp: Time.now.to_i + 3600 },
"my-secret-key",
"HS256"
)
puts token
# => eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIi...
payload, header = JWT.decode(token, "my-secret-key", true, algorithm: "HS256")
puts header
# => {"alg"=>"HS256"}
セグメントは JSON の base64url にすぎないので、トークンの内部で Base64 層が仕事をするのをそのまま観察することもできます:
require "base64"
require "json"
payload_json = JSON.generate({ "sub" => "1234567890", "name" => "Alice" })
segment = Base64.urlsafe_encode64(payload_json, padding: false)
puts segment
# => eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0
このユースケースには 2 つのルールが属します。本番で JWT を手作りしないでください - 署名こそが、トークンを白状ではない何かにしてくれるもの - そして gem でデコードするときは、上記のようにオプションハッシュでアルゴリズムをピン留めしてください。そうすれば、トークン自身のヘッダーが検証方法を決めることはできません。
HTTP Basic 認証:ヘッダーを作る
HTTP で「私は誰だ」と言う最も古い方法は、今なお最もシンプルです:認証情報を Base64 化して、Basic という単語の後ろに置いて、ヘッダーを送る。Ruby で作るのに 1 行で済みます:
require "base64"
credentials = Base64.strict_encode64("alice:s3cr3t!")
puts "Basic #{credentials}"
# => Basic YWxpY2U6czNjcjN0IQ==
Ruby の標準ライブラリも、Net::HTTP でまさにこれをしており、コアの pack テンプレートを直接呼び出します - basic_auth は中身が ["user:pass"].pack("m0") に集約されるのです:
require "net/http"
request = Net::HTTP::Get.new("https://example.org/api")
request.basic_auth("alice", "s3cr3t!")
puts request["Authorization"]
# => Basic YWxpY2U6czNjcjN0IQ==
そしてセキュリティに関する注記を、記録に残すために 1 度だけ述べておきます:Base64 は翻訳者であり、鍵(ロック)ではありません。Basic 認証ヘッダーの中の認証情報は、パケットを読める誰にでも読めます。このヘッダーが許されるのは、トランスポートが実際に保護をしてくれる HTTPS の上だけです。
Data URI:インラインの画像とフォント
Data URI は、Web が「この画像を別ファイルなしで使いたい」という願いへの答えです:メディアタイプ、base64 という単語、カンマ、そしてバイト。1 ファイル HTML デモがロゴを載せる方法、ファビコンが CSS の中に隠れる方法、生成された画像がテンプレート文字列の中でまるごと暮らす方法 - それらがすべて、これによるのです:
require "base64"
png = File.binread("logo.png")
data_uri = "data:image/png;base64,#{Base64.strict_encode64(png)}"
css = "background-image: url(#{data_uri});"
puts css.length
# => あなたのスタイルシートから、HTTP リクエストを 1 回減らしたもの
ここでは strict_encode64 を使ってください - ペイロードは 1 行のきれいなラインで、折り返しも改行もないからです。そしてサイズに注意:インライン化する画像は約 1 分の 1 大きくなるので、data URI は小さいアセット(ファビコン、ロゴ、アイコンフォント)には輝き、大きいものでは膨れ上がります。2 メガバイトのヒーロー写真は、あなたの HTML ドキュメントの 2.7 メガバイトになり、ユーザーは 4G の最初のスクロールでそれを感じ取ります。
メール:Base64 の発祥の地
この記事の他のすべてのユースケースは、このものの後継です。SMTP は 1980 年代に 7 ビットのテキストの短い行のために設計され、つまり JPEG を載せられない、というものでした。修正策 - Privacy-Enhanced Mail、そして 1993 年の MIME - は、バイナリを 64 文字のアルファベットを持つテキストとして書き直すことで、それがまさにあなたが今日使っている形式です。その傷跡は、今でも Ruby の出力に見えます:encode64 は 60 文字で折り返します - あとで見るように、ある特定のプロトコルに奉仕する長さではなく、ただメールを礼儀正しく保つのに短い長さです。
実際には、MIME の仕事を mail gem に任せることになります。バイナリファイルを添付すると、gem が Base64 エンコーダーを選び、行を折り返し、ヘッダーを書きます:
# Gemfile に:gem "mail"
require "mail"
message = Mail.new do |m|
m.from = "dev@example.org"
m.to = "ops@example.org"
m.subject = "Binary report"
m.add_file("report.bin")
end
puts message.encoded
# 添付ファイル部分は Content-Transfer-Encoding: base64 を持つ
ヘッダーの中の ASCII 以外のテキストは、少し衣装が違うだけで同じ扱いを受けます:RFC 2047 のエンコードドワードです。これは Base64 を質問符のあいだのキャラセットタグで包みます。=?UTF-8?B?w7wgc2VjcmV0cw==?= のように。もしあなたがそれらを手作りで構築したり解析したりするなら、中身の Base64 は普通の種類で、decode64 でデコードして、それからその単語が宣言するキャラセットで付け替えラベルを付け直します。
鍵と証明書のための PEM 鎧
鍵と証明書は PEM の鎧をまといます。その鎧は、枠付きの Base64 です:BEGIN 行、64 文字の行に並んだエンコードされたバイト、END 行。生の DER バイトから PEM ファイルを作る必要があるなら、構成は 2 ステップのラップです:
require "base64"
der_bytes = File.binread("server.der")
body_lines = Base64.strict_encode64(der_bytes).scan(/.{1,64}/)
pem = (["-----BEGIN PRIVATE KEY-----"] + body_lines +
["-----END PRIVATE KEY-----"]).join("\n") + "\n"
File.write("server.key", pem)
2 つの注記。第一に、これが必要になることはほとんどありません。openssl gem が PEM を書いてくれるからです(key.to_pem)。そして BEGIN 行と END 行のあいだのラベルは、中身と一致しなければなりません - 間違えると、インターネット上のすべてのツールが拒否するファイルが生まれます。第二に、ここでの行長は 64、つまり PEM の伝統的な幅です。Ruby の encode64 は代わりに 60 で折り返しますが、まともな PEM パーサーはすべて行長を完全に無視するので、どちらの幅でも問題なくデコードできます。
ファイル:.b64 慣習
Base64 の世界で最も一般的なファイル形式は、1 つのエンコードされたペイロードを含む、.b64(または .base64)拡張子のプレーンテキストファイルです - 「どこに貼り付けても安全なファイル」と思って下さい。Ruby で作るのに 1 行で済みます:
require "base64"
File.write("payload.b64", Base64.strict_encode64(File.binread("payload.bin")))
puts File.size("payload.b64")
# => 元のサイズの約 1.33 倍
ファイルが 1 行のきれいなラインを持つように strict_encode64 を使ってください - ほとんどのデコードツール(そして Ruby の厳格デコーダー)が期待する慣習です。読み戻すのは鏡像です:読んで、デコードして、出す途中で何ひとつ変更されないよう、バイナリモードでバイトを書きます:
encoded = File.read("payload.b64")
bytes = Base64.strict_decode64(encoded)
File.binwrite("restored.bin", bytes)
あなたの .b64 ファイルが行を折り返すツールから来たなら - base64 CLI のある種のバリアントがそうします - 厳格デコードの前に改行を取り除いてください。それとも、それらを無料ですき飛ばしてくれる寛容なデコーダーを使うことです。
設定ファイル、環境変数、データベース
バイナリデータがテキスト文書の中に住まなければならないときは、Base64 が橋になります。このパターンは、小さな変化を付けて 3 か所で繰り返されます。
環境変数と .env ファイルは生のバイトを保持できないので、バイトは、それを保有しているマシンを離れる前にエンコードされます:
require "base64"
# アプリをプロビジョニングするところ
ENV["APP_LOGO"] = Base64.strict_encode64(File.binread("logo.png"))
# アプリが起動するところ
b64 = ENV.fetch("APP_LOGO")
File.binwrite("logo.png", Base64.decode64(b64))
YAML はネイティブのバイナリ型を持っていて、Psych が Base64 をやってくれます - BINARY 文字列を YAML に dump すると !binary スカラーとして出て、読み戻すとバイト単位で同一になります:
require "yaml"
yaml_text = YAML.dump({ "logo" => File.binread("logo.png") })
puts yaml_text.lines.first(2)
# => "---"
# => "logo: !binary |-"
data = YAML.load(yaml_text)
puts data["logo"].encoding
# => ASCII-8BIT
データベースでは、問いはエンコードではなくストレージの型です。データベースに本物のバイナリ列 - BLOB、BYTEA、VARBINARY - があるなら、それを使い、バイトの運搬はドライバに任せましょう。TEXT 列に Base64 を入れるのは、ストレージ層が文字列しか話さないときのパターンです:ある種のドキュメントストア、JSON 型の API、あるいは変えられないレガシーなスキーマ。代価は、列にかかる 3 分の 1 のサイズ税と、すべての境界で例外なく、入るときはエンコードし、出るときはデコードするという規律です。
テキストとして旅するチェックサム
ハッシュはバイナリですが、チェックサムはほぼテキストの中を旅します:ファイル完全性のリスト、キャッシュキー、フィンガープリント、ログの行。Ruby のダイジェストクラスはそれぞれ 1 回の呼び出しでエンコードをする base64digest メソッドを持っています:
require "digest"
Digest::SHA256.base64digest("hello")
# => "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
出力はパディング付きの標準 Base64 - Base64.strict_encode64(Digest::SHA256.digest("hello")) で得られるものと同じ - なので、保存も比較も貼り付けも安全です。唯一の判断は一貫性です:Base64 で生成したチェックサムのリストは Base64 の出力と照合しなければなりませんし、同じハッシュの 16 進表記と Base64 表記は別の文字列なので、1 つを選んで貫きましょう。
小さなチャンクで大きなものをエンコード
デコーダーと同じく、エンコーダーもバッファベースです:入力を全部読んで、出力を全部出します。標準ライブラリにストリーミングのエンコーダーはないので、大きなペイロードでは計画の対象はメモリになり、その計算には心地よい対称性があります。エンコードはデータを 1 分の 1 増やすので、最大の割り当ては出力であり入力ではありません。1 ギガバイトのファイルなら、目の前に約 1.33 ギガバイトのテキストが並ぶと期待すべきです。
それを一度に全部抱えるのがきついなら、チャンク単位でエンコードできます。Base64 のアルファベットは 3 バイトの境界で自己同期するからです:3 バイトのスライスをそれぞれ独立にエンコードして連結すると、全体をエンコードした場合と同一になります:
require "base64"
require "securerandom"
bin = SecureRandom.random_bytes(10_001)
whole = Base64.strict_encode64(bin)
chunked = bin.scan(/.{1,3}/m).map { |slice| Base64.strict_encode64(slice) }.join
puts chunked == whole
# => true
同じトリックで、encode64 と完全に一致する自作の行ラッパーが得られます:45 バイトは必ずちょうど 60 文字にエンコードされるので、入力を 45 バイトでスライスして、切れ目を改行でつなぎ合わせれば、1 行ずつ、メモリには一度に 1 つのスライスしか持たずに、古典的な MIME 出力を再現できます:
def wrap_like_encode64(bin)
lines = bin.scan(/.{1,45}/m).map { |slice| Base64.strict_encode64(slice) }
lines.join("\n") + "\n"
end
bin = SecureRandom.random_bytes(10_001)
puts wrap_like_encode64(bin) == Base64.encode64(bin)
# => true
コマンドラインから
エンコードにもスクリプトファイルは不要です。ワンライナーの形はファイルを読んで、その Base64 を標準出力に書きます:
ruby -rbase64 -e 'print Base64.strict_encode64(File.binread(ARGV[0]))' payload.bin > payload.b64
そしてパイプの形は標準入力を読みます。他のどのコマンドからでも、バイトのストリームを包む方法がこれです:
some_command | ruby -rbase64 -e 'print Base64.strict_encode64(STDIN.read)'
どちらも print を保ってください - うっかり puts にすると、Base64 の末尾に改行が付いて、strict_encode64 の出力では、きれいなトークンが壊れたトークンになります。デコード側と同じ格言です:出力の次の消費者が厳格なら、Base64 自身以外は何も同伴できません。
Ruby 開発者に余計なバイトを払わせる罠
- JSON の末尾改行。
Base64.encode64は空でないすべての結果を改行で終わらせるので、きれいなトークンであるべき値が、末尾に予想外の\nを付けてあなたの JSON にやって来ます。1 行で保存、比較、送信されるものはすべてstrict_encode64にしてください。 - トークンと URL での 60 文字の折り返し。同じメソッドは長い出力を複数行に折り返します。URL の中の折り返された文字列は 2 つの URL で、折り返されたトークンは壊れたトークンです。繰り返します:
strict_encode64、あるいはencode64の出力に縛られているなら、改行をstrip/deleteしてください。 - URL 中のプラスとスラッシュ。クエリ文字列の中の標準 Base64 は、出るときに
%2B、%2F、%3Dをパーセントエンコーディングして、相手側がそれらをデコードしてくれるのを祈ることになります。urlsafe_encode64は問題を根本から取り除きます。 - 間違った場所のパディング。JWT や他のトークン方式はパディングオフを望み、MIME の読み手はパディング不足に対応できないかもしれません。
padding: falseを出すのは仕様がそれを要求する場所だけにし、あなたの各消費者がその塀のどの側にあるのかを知っておくこと。 - 文字はバイトではない。アクセント付きの文字を 1 つ持つ 5 文字の文字列は UTF-8 では 6 バイトで、出力の長さの計算はバイトで動きます。エンコード結果が「長すぎる」ときは、文字ではなくバイトを数えてください。
- スキーマ設計における 3 分の 1 の税金。16 KB の BLOB は、TEXT 列でおよそ 22 KB の Base64 文字列になります。列、キャッシュ、API のペイロードは、バイナリ形ではなくエンコード後の形に合わせてサイズを決めましょう。
- 2 つのアルファベット、2 つの異なる文字列。同じバイトでも、標準アルファベットと URL 安全なアルファベットではエンコード結果が異なるので、エンコードされた値は同じアルファベットのもう 1 つの値とだけ比較できます。決して比較せず、混ぜることもしないでください。
- Base64 は鍵(ロック)ではない。秘密をエンコードしても、それは秘密になりません。その文字列を持つ人はあなたのデータを持っているのです。Base64 が制御するのは、バイトの外見だけで、それを読める人が誰かではありません。
バイトとバグを節約する習慣
strict_encode64をデフォルトにしましょう。出力が URL、cookie、識別子の中に住むとわかった瞬間にurlsafe_encode64に切り替え、行き先がメールのような行指向のテキストプロトコルのときだけencode64にします。- 線の両端で、エンコーダーとデコーダーのあいだにアルファベットの一貫性を保ちましょう。「Base64 が壊れた」バグで最も多い 1 つは、標準アルファベットの生産者と URL 安全な消費者が出会う、あるいはその逆です。
- エンコーダーには、エンコードするつもりのあるバイトを渡しましょう:ファイルは
File.binread、組み立てたバイナリはpack、文字列そのものがデータなら UTF-8 の文字列。エンコーダーはあなたの選択を問い詰めることはありません - ただバイトを数えるだけです。 - 増加に予算を付けてください。Base64 文字列が境界を越えてサイズのあるコンテナに入るたびに、4/3 をかけて、パディングの余白を少し足します。
- Base64 は可搬性のために使い、秘密のために決して使わないでください。データを非公開に保つのが目的なら、その道具は暗号化で、Base64 はその後、暗号文に対してやることだけです。
Base64 が gem になるまで
生きている間のほとんどを、Base64 モジュールは標準ライブラリの中のただのファイルでした。Ruby の最も古いヘルパーの多くがそうであるように。厳格版と URL 安全版のメソッドは、1.9 の開発ラインのあいだにオリジナルの 2 つ組に加わりました - 4 つのメソッドをすべて備えた base64 ライブラリ全体が 2008 年 9 月に trunk に追加され、1.9.1(2009 年)で初めて出荷され、padding: キーワードは 2015 年の Ruby 2.3 で到着しました。あなたが今日見る API のすべてが、それまでに落ち着いていたのです - 残りの物語は、このモジュールがどう出荷されるかという話です。
2020 年、Ruby 3.0 のとき、コアチームは標準ライブラリを自分の gem に抽出する動きを始め、base64 もその 1 つになりました:バージョン 0.1.0、コアの貢献者たちが ruby/base64 リポジトリでメンテナンスしています。default gem として出荷されました - Ruby と一緒に配布され、常に利用可能なので、require "base64" は何の手続きもなく動き続けました。バージョン 0.2.0 は 2023 年の Ruby 3.3 で続き、Base64::VERSION 定数と、ずっと豊かなドキュメント群を追加しました。
そして 2024 年 12 月の Ruby 3.4 が、線を引き直しました:base64 は default gem のリストから bundled gem のリストに移り、csv や drb と同じ棚に置かれました。bundled gem はなお言語と一緒に出荷されますが、Bundler ベースのプロジェクトはそれらを宣言することが期待されるので、あなたが Ruby 3.4 以降で、アプリが Bundler 駆動なら、Gemfile に gem "base64" を足してください(または gem install base64 を実行)- これでカバーされます。2025 年の Ruby 4.0 がバージョン 0.3.0 をもたらしました。RBS 型シグネチャ付きで、静的チェッカーがこのモジュールをちゃんと見えるようにするためです。
この旅の全体を通して、実装は、いつもそうだったままでした:コアの pack と unpack テンプレートを包む、数十行の純 Ruby。C 拡張もなし、依存関係もなし - そして rubygems.org でのダウンロード数が数億回に達する - 最もインストールされている gem の 1 つです。
楽しい Ruby の事実
- モジュール全体、エンコーダーも含めて、コーヒーブレイク 1 回で読めるほど短いです。
encode64は文字通り[bin].pack("m")、strict_encode64は[bin].pack("m0")、urlsafe_encode64は厳格なエンコーダーの上に 2 文字の交換を施し、求めればパディングを除いたもの、です。 encode64の 60 文字の折り返しは、MIME の 76 文字の上限にも、PEM の伝統的な 64 にも一致しません。それは単に、mpack テンプレートが昔からそうやってきたことで、mailgem の Base64 エンコーダーはそれを賛辞をもってコメントしています:Ruby の自動行折り返しが、出力を SMTP の制限の内に保つ。- Ruby の
Net::HTTPは、Basic 認証のためにBase64モジュールを使う手間すら取りません -packテンプレートを直接呼び出します。これは、モジュールはコアの上の便利層であって、その逆ではない、という良いリマインダーです。 - すべてのダイジェストクラスに
base64digestメソッドがあるので、Digest::SHA256.base64digestはhexdigestと並ぶ一流市民です - 2 回目の呼び出しなしの、テキストの中のチェックサム。 - YAML の
!binaryタグは偽装した Base64 です。BINARY 文字列を dump した瞬間に Psych がエンコードしてくれるので、バイナリでいっぱいの設定ファイルは、あのようになるのです。 - あなたが使っているモジュールは、あなたが覚えているモジュールとは限りません。古い Ruby には
b64encode(選んだ幅で折り返す)とdecode_b(RFC 2047 ヘッダーのデコード)がありました。どちらも 1.9 シリーズで消え去ったので、それらを呼び出す 2010 年以前の、あなたが継承したコードはNoMethodErrorで死にます。 - YouTube の動画 ID はパディングなしの base64url です - 11 文字、プラスもスラッシュもイコールもない - まさに、URL 安全なアルファベットが設計されたための、短いリンク安全な識別子の典型です。
逆方向の話
これであなたは、エンコードの完全な全体像を持ちました:決してあなたを驚かせないデフォルトの主力、それを求めるプロトコルのために行を折り返す古典、パディングのスイッチ付きのリンク安全なアルファベット、そしてあなたの出力がちょうどどんなものになるかを決定するバイトレベルのルール。逆方向 - Base64 文字列をばらして、Ruby の 3 つのデコーダーのあいだを選び、結果のバイトをあなたが使えるものに変えること - には、決してノーと言わないデコーダーから始まる、自分だけの静かな罠があります。通りの向こう側は、下にリンクする Base64 デコードの記事で、同じ深さで扱っています。
最終更新: 2026-10-10