Base64
Base64 は、エンコード方式の1つで、64種類の印字可能な英数字記号を用いてデータをエンコードします。広く使われるエンコード方式であり、例えば電子メールのマルチバイト文字のエンコードなどに用いられます。そのほか、画像ファイルのエンコード、Basic認証などに用いられています。
例えば、UTF8の文字列 1位 を Base64でエンコードすると、MeS9jQ== となります。半角文字の "1" も、全角文字の "位" も別の半角英数に置き換えられていることがわかります。
特徴
Base64 は、データを64種類の文字(A–Z, a–z, 0–9, +, /)とパディング用の文字(=)にエンコードします。後述の通り、6bitずつに分解して対応する64種の文字に置き換えていくので、データサイズにかかわらずエンコード後のデータサイズは約33%(6bit->8bitなので)増加します。
約33%となっているのはパディング用の文字(=)が追加される場合があるためです。ただし基本3割増しのデータ量になることは覚えておきましょう。
また、すべてのデータをエンコードするので元データがテキストだったとしても、読める形にはなりません。これは、Quoted Printable が、印字可能な文字はそのまま出力することと比べ異なる点です。
Quoted Printable は、読める形を保とうとするエンコード方式の為、エンコード対象のデータによってはデータサイズが増減しますが、Base64は一律約33%データサイズが増加します。
アルゴリズムの解説
エンコード
- エンコード対象のデータを6bitずつに分解します。データが6bitに足りない分は0で埋めます。
- 例:
01100001
->011000
+010000
- 例:
- 分解して得られた各6bitのデータを、4文字ずつ対応表を使って文字に置き換えます。4文字に満たない分は "=" で埋めます。
- 例:
011000
+010000
->YQ==
- エンコード後のBase64の文字数は必ず4倍数になります。
- 例:
デコード
デコードはエンコードの手順を逆から実行していきます。
- 対応表からbitデータに置き換えます。"=" は無視します。
- 例:
YQ==
->011000
+010000
- 例:
- 8bitに満たない末尾の0は切り捨てます。
- 例:
011000
+010000
->01100001
- 例:
対応表
Base64で使う対応表は次の通りです。6bitに分解したデータを対応表に突き合わせます。デコード時もこの対応表を使うことになります。
10進 | 2進 | 文字 | 10進 | 2進 | 文字 | 10進 | 2進 | 文字 | 10進 | 2進 | 文字 | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 000000 | A | 16 | 010000 | Q | 32 | 100000 | g | 48 | 110000 | w | |||
1 | 000001 | B | 17 | 010001 | R | 33 | 100001 | h | 49 | 110001 | x | |||
2 | 000010 | C | 18 | 010010 | S | 34 | 100010 | i | 50 | 110010 | y | |||
3 | 000011 | D | 19 | 010011 | T | 35 | 100011 | j | 51 | 110011 | z | |||
4 | 000100 | E | 20 | 010100 | U | 36 | 100100 | k | 52 | 110100 | 0 | |||
5 | 000101 | F | 21 | 010101 | V | 37 | 100101 | l | 53 | 110101 | 1 | |||
6 | 000110 | G | 22 | 010110 | W | 38 | 100110 | m | 54 | 110110 | 2 | |||
7 | 000111 | H | 23 | 010111 | X | 39 | 100111 | n | 55 | 110111 | 3 | |||
8 | 001000 | I | 24 | 011000 | Y | 40 | 101000 | o | 56 | 111000 | 4 | |||
9 | 001001 | J | 25 | 011001 | Z | 41 | 101001 | p | 57 | 111001 | 5 | |||
10 | 001010 | K | 26 | 011010 | a | 42 | 101010 | q | 58 | 111010 | 6 | |||
11 | 001011 | L | 27 | 011011 | b | 43 | 101011 | r | 59 | 111011 | 7 | |||
12 | 001100 | M | 28 | 011100 | c | 44 | 101100 | s | 60 | 111100 | 8 | |||
13 | 001101 | N | 29 | 011101 | d | 45 | 101101 | t | 61 | 111101 | 9 | |||
14 | 001110 | O | 30 | 011110 | e | 46 | 101110 | u | 62 | 111110 | + | |||
15 | 001111 | P | 31 | 011111 | f | 47 | 101111 | v | 63 | 111111 | / |
サンプルコード
文字列を渡すと、Base64でエンコードした文字列を返してくれるエンコード処理と同じくデコード処理の実装のサンプルコードです。文字列をエンコード・デコードする場合にはその文字列の文字コードを指定する必要がありますが、子のサンプルではUTF8固定で処理しています。
対応表は64種の文字による配列としています。インデックスとその値が対応表の組み合わせに当たります。例えば"111101(62)" は、ConversionTable[62]
で "9" が得られます。逆引きの場合は "9" のインデックスを探します。
処理の詳細はコメントにある通りです。基本は上記アルゴリズムをそのままに実装しています。そこまで難しくはないかと思います。
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
class Base64
{
public char[] ConversionTable = new char[]
{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', '+', '/',
};
public string Encode(string text)
{
var result = "";
// 文字列(UTF8固定)をバイト型配列に変換
var bytes = Encoding.UTF8.GetBytes(text);
// 2進数文字列に展開
var bits = bytes
.SelectMany(octet => Convert.ToString(octet, 2).PadLeft(8, '0'))
.ToArray();
// 6bit ずつ処理する
var bit6 = "";
for (int i = 0; i < bits.Length; i++)
{
// 2進数文字列で保持する
bit6 += bits[i];
// 6bit になったら変換表から対応文字を取得し出力
if (bit6.Length == 6)
{
// 2進数文字列として変換
var tableIndex = Convert.ToInt32(bit6, 2);
result += ConversionTable[tableIndex];
bit6 = "";
}
// 最後に6bitに足りていない場合は0で埋める
if (i == bits.Length - 1 && bit6.Length != 0)
{
var padCount = 6 - bit6.Length;
for (int j = 0; j < padCount; j++) bit6 += "0";
// 2進数文字列として変換
var tableIndex = Convert.ToInt32(bit6, 2);
result += ConversionTable[tableIndex];
}
}
// 出力文字数が4の倍数になるように "=" を付け足す
while (result.Length % 4 != 0) result += "=";
return result;
}
public string Decode(string text)
{
var bytes = new List<byte>();
var bitPosition = 0; // 現在処理中のbit位置
var buff = ""; // 2進数文字列保持用
// = を削除
text = text.TrimEnd('=');
foreach (var c in text)
{
// 変換表から値を取得
var value = Array.IndexOf(this.ConversionTable, c);
// 6桁の2進数文字列に変換
var n2 = Convert.ToString(value, 2).PadLeft(6, '0');
// 1ビットずつループ
foreach (var n in n2)
{
// 8ビット中何ビット目の出力かカウント
bitPosition++;
buff += n;
// 8ビット分処理済の場合は出力し初期化
if (bitPosition == 8)
{
// 2進数文字列をバイト型に変換して保持
bytes.Add(Convert.ToByte(buff, 2));
bitPosition = 0;
buff = "";
}
}
}
// 取得したバイトデータを文字列(UTF8固定)に変換
var result = Encoding.UTF8.GetString(bytes.ToArray());
return result;
}
}