Upload a file
  1. base64hub
  2. Base64 in Code
  3. Ruby
Code

Base64 in Ruby

Ruby’s Base64 module is a thin wrapper around Array#pack and String#unpack1. The main trap is that the non-strict methods add line breaks. Outputs below come from Ruby 3.4.

Encode and decode a string

Use the strict_ methods unless you specifically want MIME output:

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 strings carry an encoding, and decoded strings always come back as BINARY (ASCII-8BIT), because Base64 does not know what the bytes mean. force_encoding("UTF-8") relabels them without copying. If you skip it, comparisons with UTF-8 literals fail and string methods treat each byte as a character.

If the source string is in another encoding (say Windows-1252 from a legacy file), call .encode("UTF-8") before encoding to Base64, or the receiver will see different bytes than you expect.

encode64 vs strict_encode64

encode64 is the MIME flavor. It inserts a newline after every 60 characters and always ends with one:

Base64.encode64("hi")          # => "aGk=\n"
Base64.encode64("x" * 60)
# => "eHh4...eHh4\neHh4eHh4eHh4eHh4eHh4\n"   (60 chars per line)
Base64.strict_encode64("hi")   # => "aGk="

That trailing \n is the most common Ruby Base64 bug: it breaks HTTP headers, signatures and JSON comparisons. Note that Ruby wraps at 60 characters, while RFC 2045 allows up to 76 and most other languages use 76. Both are valid for email; if you need exactly 76, the MIME Base64 encoder shows that format.

URL-safe Base64 with and without padding

Base64.urlsafe_encode64("<<???>>")                  # => "PDw_Pz8-Pg=="
Base64.urlsafe_encode64("<<???>>", padding: false)  # => "PDw_Pz8-Pg"
Base64.urlsafe_decode64("PDw_Pz8-Pg")               # => "<<???>>"

urlsafe_decode64 accepts input with or without padding; it adds the missing = and then decodes strictly. The padding: keyword exists since Ruby 2.3. For JWTs and other tokens, see Base64 vs Base64URL or try the Base64URL decoder.

Reading the claims of a JWT, without verifying it:

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"}

The output format {"alg" => "HS256"} is Ruby 3.4’s new Hash#inspect; older versions print {"alg"=>"HS256"}.

Encode and decode a file

Read and write in binary mode, otherwise Windows newline translation and encoding conversion can change the bytes:

puts Base64.strict_encode64(File.binread("hello.txt"))   # aGVsbG8K
File.binwrite("copy.txt", Base64.strict_decode64("aGVsbG8K"))

Large files in chunks

The Base64 module has no streaming API, but pack("m0") on chunks works as long as each chunk is a multiple of 3 bytes:

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

On a 300 KB test file, the result was identical to strict_encode64(File.binread(...)). With any other chunk size, = padding appears in the middle of the output.

pack and unpack1 directly

The module is a convenience layer. These are equivalent and need no 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)

Validation and errors

strict_decode64 raises ArgumentError with the message invalid base64 for anything that is not canonical, padded Base64. We tried six inputs and all of them raised it:

InputProblem
PDw_Pz8-Pg==URL-safe characters
aGkmissing padding
aGk=!!junk after padding
aimpossible length
aGl=non-zero padding bits
"aGVs\nbG8K"newline
def base64?(s)
  Base64.strict_decode64(s)
  true
rescue ArgumentError
  false
end

The non-strict decode64 never raises. It skips anything it does not understand, which produces garbage instead of an error:

Base64.decode64("aGk=!!")        # => "hi"
Base64.decode64("PDw_Pz8-Pg==")  # => "<<\x0F\xCF\xC3\xE0"   (should be "<<???>>")

Use decode64 only for MIME bodies with line breaks, and only when you trust the source. When a string refuses to decode, the Base64 validator points at the problem and common Base64 errors explains the usual causes.

Ruby 3.4 and Bundler

Ruby 3.4 moved base64 from the default gems to the bundled gems. Outside Bundler nothing changes. Under bundle exec, a Gemfile without it fails:

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)

Add gem "base64" to the Gemfile or gemspec, or replace the calls with pack("m0") / unpack1("m0"), which are part of core Ruby.

FAQ

Why does Base64.encode64 add \n to my string?

encode64 produces RFC 2045 output with a newline every 60 characters and at the end. Use strict_encode64 for headers, JSON and URLs; it never adds newlines.

Why do I get 'cannot load such file -- base64' on Ruby 3.4?

Since Ruby 3.4, base64 is a bundled gem rather than a default gem. Plain ruby still loads it, but under Bundler you must add gem "base64" to the Gemfile. Or skip the require and use [data].pack("m0").

How do I decode Base64 without padding in Ruby?

Base64.urlsafe_decode64 adds missing padding itself. For the standard alphabet, append "=" * (-s.length % 4) before calling strict_decode64.

Keep reading