文字列のエンコードとデコード
MIME 形式の出力が特に必要な場合を除き、strict_ 系のメソッドを使ってください。
require "base64"
enc = Base64.strict_encode64("héllo wörld")
puts enc # aMOpbGxvIHfDtnJsZA==
dec = Base64.strict_decode64(enc).force_encoding("UTF-8")
puts dec # héllo wörld
Ruby の文字列はエンコーディング情報を持っています。Base64 はバイト列の意味を知らないので、デコードした文字列は常に BINARY(ASCII-8BIT)として返ってきます。force_encoding("UTF-8") はコピーせずにラベルを付け替えます。これを省くと、UTF-8 のリテラルとの比較が失敗し、文字列メソッドは 1 バイトを 1 文字として扱います。
元の文字列が別のエンコーディング(たとえばレガシーなファイルの Windows-1252)の場合は、Base64 にエンコードする前に .encode("UTF-8") を呼んでください。そうしないと、受け取り側には想定と異なるバイト列が届きます。
encode64 と strict_encode64
encode64 は MIME 向けです。60 文字ごとに改行を挿入し、必ず改行で終わります。
Base64.encode64("hi") # => "aGk=\n"
Base64.encode64("x" * 60)
# => "eHh4...eHh4\neHh4eHh4eHh4eHh4eHh4\n" (60 chars per line)
Base64.strict_encode64("hi") # => "aGk="
この末尾の \n が Ruby の Base64 で最もよくあるバグで、HTTP ヘッダー、署名、JSON の比較を壊します。Ruby は 60 文字で折り返しますが、RFC 2045 は最大 76 文字まで認めており、他のほとんどの言語は 76 文字を使います。メールではどちらも有効です。ちょうど 76 文字が必要なら、MIME Base64 エンコーダー でその形式を確認できます。
パディングあり・なしの URLセーフ Base64
Base64.urlsafe_encode64("<<???>>") # => "PDw_Pz8-Pg=="
Base64.urlsafe_encode64("<<???>>", padding: false) # => "PDw_Pz8-Pg"
Base64.urlsafe_decode64("PDw_Pz8-Pg") # => "<<???>>"
urlsafe_decode64 はパディングの有無にかかわらず入力を受け付け、不足している = を補ってから厳格にデコードします。padding: キーワードは Ruby 2.3 からあります。JWT やその他のトークンについては、Base64 と Base64URL の違い をご覧いただくか、Base64URL デコーダー で試してください。
JWT のクレームを(検証せずに)読むには次のようにします。
require "json"
jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig"
header, payload, _sig = jwt.split(".")
JSON.parse(Base64.urlsafe_decode64(header)) # => {"alg" => "HS256"}
JSON.parse(Base64.urlsafe_decode64(payload)) # => {"sub" => "1234"}
{"alg" => "HS256"} という出力形式は Ruby 3.4 の新しい Hash#inspect によるもので、それより前のバージョンでは {"alg"=>"HS256"} と表示されます。
ファイルのエンコードとデコード
バイナリモードで読み書きしてください。そうしないと、Windows の改行変換やエンコーディング変換によってバイト列が変わることがあります。
puts Base64.strict_encode64(File.binread("hello.txt")) # aGVsbG8K
File.binwrite("copy.txt", Base64.strict_decode64("aGVsbG8K"))
大きなファイルをチャンク単位で処理する
Base64 モジュールにはストリーム用の API がありませんが、各チャンクが 3 バイトの倍数であれば、チャンクごとに pack("m0") を使えます。
File.open("big.bin", "rb") do |src|
File.open("big.b64", "wb") do |dst|
while (chunk = src.read(3 * 1024 * 64)) # 192 KiB, divisible by 3
dst.write([chunk].pack("m0"))
end
end
end
300 KB のテストファイルでは、結果は strict_encode64(File.binread(...)) と同一でした。それ以外のチャンクサイズでは、出力の途中に = のパディングが入ります。
pack と unpack1 を直接使う
このモジュールは便利なラッパーにすぎません。次のコードは上と同等で、require も不要です。
["hi"].pack("m0") # => "aGk=" (same as strict_encode64)
["hi"].pack("m") # => "aGk=\n" (same as encode64)
"aGk=".unpack1("m0") # => "hi" (same as strict_decode64)
検証とエラー
strict_decode64 は、正規形でパディング付きの Base64 でないものすべてに対して、invalid base64 というメッセージの ArgumentError を発生させます。6 つの入力を試したところ、すべてでこのエラーが発生しました。
| 入力 | 問題 |
|---|---|
PDw_Pz8-Pg== | URLセーフの文字 |
aGk | パディングの不足 |
aGk=!! | パディングの後のゴミ |
a | あり得ない長さ |
aGl= | パディングビットが 0 でない |
"aGVs\nbG8K" | 改行 |
def base64?(s)
Base64.strict_decode64(s)
true
rescue ArgumentError
false
end
strict でない decode64 は決して例外を発生させません。理解できないものは読み飛ばすため、エラーではなくおかしなデータを返します。
Base64.decode64("aGk=!!") # => "hi"
Base64.decode64("PDw_Pz8-Pg==") # => "<<\x0F\xCF\xC3\xE0" (should be "<<???>>")
decode64 を使うのは、改行を含む MIME の本文で、かつ送り元を信頼できる場合だけにしてください。文字列がどうしてもデコードできないときは、Base64 バリデーター が問題箇所を示し、よくある Base64 のエラー が典型的な原因を説明しています。
Ruby 3.4 と Bundler
Ruby 3.4 で、base64 は default gem から bundled gem に移りました。Bundler の外では何も変わりません。bundle exec のもとでは、Gemfile に記載がないと失敗します。
warning: base64 was loaded from the standard library, but is not part of the
default gems starting from Ruby 3.4.0.
...
cannot load such file -- base64 (LoadError)
Gemfile か gemspec に gem "base64" を追加するか、Ruby のコアに含まれる pack("m0") / unpack1("m0") に呼び出しを置き換えてください。
よくある質問
Base64.encode64 で文字列に \n が付くのはなぜですか?
encode64 は RFC 2045 形式の出力を生成し、60 文字ごとと末尾に改行を入れます。ヘッダー、JSON、URL には、改行を一切付けない strict_encode64 を使ってください。
Ruby 3.4 で「cannot load such file -- base64」になるのはなぜですか?
Ruby 3.4 から、base64 は default gem ではなく bundled gem になったからです。素の ruby では今でも読み込めますが、Bundler のもとでは Gemfile に gem "base64" を追加する必要があります。あるいは require をやめて [data].pack("m0") を使ってください。
Ruby でパディングのない Base64 をデコードするには?
Base64.urlsafe_decode64 は不足しているパディングを自分で補います。標準アルファベットの場合は、strict_decode64 を呼ぶ前に "=" * (-s.length % 4) を付け足してください。