[C#] Base64のアルゴリズムをを実装する

[C#] Base64のアルゴリズムをを実装する

Base64 のエンコード・デコード

Base64 というエンコード方式をC#で実装します。Base64は、64種類の印字可能な英数字のみを用いてエンコードします。

Quoted-printable と同じくメール送信時のエンコードとして用いられるほか、Basic認証や画像ファイルの埋め込みなどにも用いられています。

ここでは、Base64のエンコード・デコード処理を独自に実装していきます。

なお、.NET Framework では、Convert.ToBase64String メソッドConvert.FromBase64String メソッド がそれぞれ用意されており、独自実装を使わなくてもエンコード・デコードが可能です。

Base64 の仕様、アルゴリズム

Base64変換の手順を以下に挙げる。

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

Wikipediaからの引用です。これに沿って実装します。手順2. の4文字ずつというのは、最終出力のBase64の文字数が4の倍数になるように = でパディングされることを意味します。

対応表

Base64では次の対応表を使います。6ビットの数値と64種類の文字を対応付けます。

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 /

エンコードの例

文字列(UTF8) aあ をBase64でエンコードする手順を以下に記します。

  1. Byte配列に変換
    [97, 227, 129, 130]
  2. 2進数文字列に変換する
    01100001 11100011 10000001 10000010
  3. 6ビットずつに分割して余った部分には0を追加
    011000 011110 001110 000001 100000 10 + 0000
  4. 対応表から4文字ずつ変換し、4文字に満たない部分は “=” を追加
    YeOB + gg + ==
  5. 完成
    YeOBgg==

デコードの例

Base64文字列 YeOBgg== をデコード(UTF8)する手順を以下に記します。

  1. Base64文字列の末尾から “=” を取り除く。
    YeOBgg
  2. 対応表から各文字を2進数文字列に変換する。
    011000 011110 001110 000001 100000 100000
  3. 8ビットずつにまとめる。
    01100001 11100011 10000001 10000010
  4. Byte配列に変換する。
    [97, 227, 129, 130]
  5. UTF8文字列として変換する。
    aあ

C# エンコード処理の実装

対応表は単純な配列で表現します。インデックスが値で要素が対応する文字です。

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, Encoding encoding)
{
    var sb = new StringBuilder();

    // 文字列をバイト型配列に変換
    var bytes = encoding.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);
            sb.Append(ConversionTable[tableIndex]);

            bit6 = string.Empty;
        }

        // 最後に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);
            sb.Append(ConversionTable[tableIndex]);
        }
    }

    // 出力文字数が4の倍数になるように "=" を付け足す
    while (sb.Length % 4 != 0) sb.Append("=");

    return sb.ToString();
}

C# デコード処理の実装

デコード処理で使う対応表は、エンコード時に使ったものと同じです。

2重のループで1ビットずつ処理しています。8ビット分でまとめて対応表から変換を行い出力するといった手順です。

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 Decode(string text, Encoding encoding)
{
    var bytes = new List<byte>();
    var octet = 0x00;
    var bitPosition = 0;

    // = を削除
    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++;

            // ビットが立っている場合は処理
            if (n == '1')
            {
                // (左から)3ビット目が1の場合、
                // 1000 0000 (128)を左に2ビットシフトした
                // 0010 0000 を足し合わせる
                octet += 128 >> (bitPosition - 1);
            }

            // 8ビット分処理済の場合は出力し初期化
            if (bitPosition == 8)
            {
                bytes.Add((byte)octet);
                octet = 0;
                bitPosition = 0;
            }
        }
    }

    var result = encoding.GetString(bytes.ToArray());

    return result;
}

以上。

参考URL

C#カテゴリの最新記事