ファイルを選択
  1. base64hub
  2. 各言語での Base64
  3. Ruby
コード

Ruby での Base64

Ruby の Base64 モジュールは、Array#pack と String#unpack1 を薄く包んだものです。最大の落とし穴は、strict でないメソッドが改行を付けることです。以下の出力は Ruby 3.4 のものです。

文字列のエンコードとデコード

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) を付け足してください。

続けて読む