演習: Base64エンコーディングについて

Base64については前述の通りですが、かなり簡単な実装のため、ここではPythonで確認してみましょう。

ライブラリによる変換

Pythonでは、標準でbase64というモジュールが用意されており、インポートすることでBase64のエンコード・デコードが可能です。

import base64

Base64でエンコードするには、対象となる文字列はバイト列でないといけません。そのため、文字列をバイト列に変換する必要があります。

# 文字列をバイト列に変換
byte_string = "hello, world".encode("utf-8")

このバイト列をBase64でエンコードするには、base64.b64encode関数を使います。

# Base64でエンコード
b64_encoded = base64.b64encode(byte_string)
print(b64_encoded)  # b'aGVsbG8sIHdvcmxk'

逆にデコードするときは、base64.b64decode関数を使います。 渡すのはエンコードされた文字列であり、型はバイト列である必要があります。

# Base64でデコード
decoded_bytes = base64.b64decode(b64_encoded)
print(decoded_bytes)  # b'hello, world'

デコードされたバイト列を文字列に戻すには、decodeメソッドを使います。

# バイト列を文字列に変換
decoded_string = decoded_bytes.decode("utf-8")
print(decoded_string)  # hello, world

これらをまとめると、以下のようになります。

b64-encode.py ※ライブラリのみ使用
# 引数で渡された文字列を、Base64で符号化してください
# augumentparserを使って引数の文字列を受け取ります。
import argparse
import base64


def main():
    parser = argparse.ArgumentParser(description="Base64 encode a string")
    parser.add_argument("string", type=str, help="String to be encoded")
    args = parser.parse_args()

    # 文字列をバイトに変換
    byte_string = args.string.encode("utf-8")

    # Base64で符号化
    b64_encoded = base64.b64encode(byte_string)

    # バイトを文字列に変換して出力
    print(b64_encoded.decode("utf-8"))


if __name__ == "__main__":
    main()
b64-decode.py
#!/usr/bin/env python
"""Base64デコードを行う
引数で渡されたBase64で符号化された文字列をデコードして元の文字列に戻す
"""

import argparse
import base64
import binascii
import sys


def main() -> None:
    parser = argparse.ArgumentParser(description="Base64 decode a string")
    parser.add_argument("string", type=str, help="Base64 encoded string")
    args = parser.parse_args()
    b64_string = args.string
    # Base64でデコード
    try:
        decoded_bytes = base64.b64decode(b64_string, validate=True)
        # バイトを文字列に変換して出力
        print(decoded_bytes.decode("utf-8"))
    except (binascii.Error, UnicodeDecodeError) as e:
        print(f"Error decoding Base64 string: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()

アルゴリズムの実装

Base64のアルゴリズムは非常にシンプルなので、自分で実装することもかなり簡単です。 以下は、Base64のエンコードを自分で実装した例です。

b64-encode.py ※アルゴリズムを自前で実装(抜粋)
 1def base64_self(s: bytes) -> str:
 2    """自分でBase64エンコードしてみる
 3
 4    Args:
 5        s (str): エンコードしたい文字列
 6    Returns:
 7        str: Base64エンコードされた文字列
 8    """
 9
10    # バイト列をビット列に変換
11    bits = "".join(format(byte, "08b") for byte in s)
12
13    # 6ビットずつに分割
14    base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
15    #               ^^^^^^^^^^^^^^^^^^^^^^^^^^--------------------------^^^^^^^^^^--
16    #               英大文字(26文字)            英小文字(26文字)            数字(10)   +/ ということで64種類の文字
17    encoded = ""
18    for i in range(0, len(bits), 6):
19        chunk = bits[i : i + 6]
20        if len(chunk) < 6:
21            chunk += "0" * (6 - len(chunk))  # パディング
22        index = int(chunk, 2)
23        encoded += base64_chars[index]
24
25    # パディング文字を追加
26    padding = (3 - len(s) % 3) % 3
27    encoded += "=" * padding
28
29    return encoded

要所の解説です。

bits = "".join(format(byte, "08b") for byte in s)

ここでは、バイト列をビット列に変換しています。各バイトを8ビットの2進数に変換し、それらを連結しています。 bitsは0か1の文字列になります。

(変換例)
0110100001101111011001110110010101101000011011110110011101100101

これを6ビットずつに区切ります。

for i in range(0, len(bits), 6): # 0,6,12, ...
        chunk = bits[i : i + 6] # 6ビット分取得する
        if len(chunk) < 6:  # 足りないときは末尾に0をパディング
            chunk += "0" * (6 - len(chunk))  # パディング

変換先となる文字列は、Base64では以下のように定義されています(英字52文字+数字10文字+記号2文字)。

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

得られたchunkを10進数に変換し、その値を上記の文字列から取り出して連結します。

        index = int(chunk, 2)
        encoded += base64_chars[index]

最後に、エンコード後の文字列の長さが4の倍数になるように、=でパディングします。

    padding = (3 - len(s) % 3) % 3
    encoded += "=" * padding

こうすることで、Base64のエンコードが完了します。