base64エンコードのアルゴリズムをRubyで実装する

Base64とは英数字、記号を用いてマルチバイト文字やバイナリデータ(画像など)を扱うためのエンコード方式です。
具体的にはA–Z, a–z, 0–9 までの62文字と、記号2つ (+,/)、さらにパディング(余った部分を詰める)のための記号として = が用いられます。
7ビットの文字コードしか扱うことができない電子メールにおいてよく利用されています。

変換アルゴリズム

変換アルゴリズムは以下となります。

  1. 元データを6ビットずつに分割する(6ビットに満たない部分は0を追加して6ビットにする)。
  2. 各6ビットの値を変換表を使って4文字ずつに変換する(4文字に満たない部分は=記号を使って4文字にする)。

変換例

1. 元データ

文字列: “ABCDEFG”
2進数に変換する: “0100 0001 0100 0010 0100 0011 0100 0100 0100 0101 0100 0110 0100 0111”

rubyでのサンプルコード

1
"ABCDEFG".unpack("B*").pop.scan(/.{1,4}/).join(" ")

2. 6ビットずつに分割

“010000 010100 001001 000011 010001 000100 010101 000110 010001 11”

1
"ABCDEFG".unpack("B*").pop.scan(/.{1,6}/).join(" ")

3. 2ビット余るので、4ビット分0を追加して6ビットにする

“010000 010100 001001 000011 010001 000100 010101 000110 010001 110000”

1
list = "ABCDEFG".unpack("B*").pop.scan(/.{1,6}/).join(" ").split.map { |s| sprintf("%-06s", s).gsub(" ", "0")}.join(" ")

4. 変換表により、4文字ずつ変換

“QUJD”, “REVG”, “Rw”

1
2
3
4
5
6
7
# 変換表を作成する
keys = (0..63).map {|m| sprintf("%06s", m.to_s(2)).gsub(" ", "0")}
values = [('A'..'Z'), ('a'..'z'), ('0'..'9'), ['+', '/']].map { |a| a.to_a }.flatten
base64_table = Hash[[keys, values].transpose]

base64_list = list.map {|a| base64_table[a]}.join.scan(/.{1,4}/)
=> ["QUJD", "REVG", "Rw"]

5. 2文字余るので、2文字分 = 記号を追加して4文字にする

1
base64_list.map {|s| sprintf("%-4s", s).gsub(" ", "=")}

6. Base64文字列

“QUJDREVGRw==”

1
2
base64_str.scan(/.{1,4}/).map {|s| sprintf("%-4s", s).gsub(" ", "=")}.join
=> "QUJDREVGRw=="

簡易的なbase64_decodeメソッド

今までのロジックをメソッドにまとめて簡易的なbase64_decodeメソッドを作成しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base64
def self.base64_encode(str)
# 変換表を作成する
keys = (0..63).map {|m| sprintf("%06s", m.to_s(2)).gsub(" ", "0")}
values = [('A'..'Z'), ('a'..'z'), ('0'..'9'), ['+', '/']].map { |a| a.to_a }.flatten
base64_table = Hash[[keys, values].transpose]

binary = str.unpack("B*").pop.scan(/.{1,6}/).join(" ").split.map { |s| sprintf("%-06s", s).gsub(" ", "0") }
base64_list = binary.map {|a| base64_table[a]}.join.scan(/.{1,4}/)
base64_list.map {|s| sprintf("%-4s", s).gsub(" ", "=")}.join
end
end

p Base64.base64_encode("ABCDEFG")
=> "QUJDREVGRw=="

RubyのBase64ライブラリでencodeした値と比べてみましょう。

1
2
3
require 'base64'
Base64.encode64("ABCDEFG")
=> "QUJDREVGRw==\n"

Rubyのencode64は最後に改行が入るようですが、encodeされた値は同じですね!

参考リンク