AIが作った連携コードでBase64を使うときは、元の文字列、文字コードで変換したバイト列、Base64文字列を分けます。文字数とバイト数は同じとは限らず、Base64から戻せても、戻したバイト列がUTF-8の文章とは限りません。
この記事では、日本語を含む架空の短文と、説明用の2バイトをローカルで変換します。標準Base64とURL-safeの違い、パディング、元バイト列への復元を確かめる例です。実ファイルの送信、API接続、認証、AIサービス、画面表示は検証していません。
目次
元の文字列と、送るバイト列を決める
文字列の例は「赤A」です。UTF-8へ変換すると、赤は3バイト、Aは1バイトで合計4バイトになります。画面上の2文字という数を、そのままデータサイズには使いません。
表は横にスクロールできます
| 段階 | 値 | 長さの意味 |
|---|---|---|
| 元文字列 | 赤A | この例のPython文字列では2文字 |
| UTF-8バイト列 | e8 b5 a4 41 | 4バイト |
| 標準Base64 | 6LWkQQ== | ASCIIの8文字 |
| 復元したバイト列 | e8 b5 a4 41 | 元の4バイトと一致 |
| UTF-8で読んだ文字列 | 赤A | 元の文字列と一致 |
Base64はバイト列を文字で表現する符号化です。暗号化ではなく、機密性を与えません。RFC 4648も、見た目を変えても情報を秘匿できない点を説明しています。認証情報をBase64にしたから共有できるとは扱わないでください。RFC 4648:Security Considerations(新しいタブで開く)
この例は説明用の文字列だけを使います。既存の認証値や実際の顧客データを読み取って変換する手順ではありません。
文字列はUTF-8へ変換してからBase64にする
Pythonのbase64モジュールは、バイト列をBase64へ変換し、復号時もバイト列を返します。文字として読みたい場合は、その後で文字コードを指定します。Python 3.9公式:base64(新しいタブで開く)
import base64
text = "赤A"
raw = text.encode("utf-8")
encoded = base64.b64encode(raw).decode("ascii")
restored = base64.b64decode(encoded, validate=True)
restored_text = restored.decode("utf-8")
assert raw.hex() == "e8b5a441"
assert encoded == "6LWkQQ=="
assert restored == raw
assert restored_text == text
print(len(text), len(raw), len(encoded))
print(encoded, restored.hex(), restored_text)
今回の長さは2、4、8です。復元前後を比較する対象はまずバイト列です。文字として扱う場合だけ、同じUTF-8の条件で文字列まで照合します。改行を足す、文字コードを変える、Unicodeを正規化する操作は、この往復変換に勝手に加えません。
Base64をもう一度Base64へ変換すれば、表現の階層が一段増えます。一度戻しても元の文章へ届かない場合は、何回か復号して読めるまで試す前に、入力契約と各段階の型を確認してください。
末尾のイコールと、文字数の増加を確認する
ここでは、改行のない標準Base64で、必要なパディングを付けます。元がnバイトなら文字数は4 × ceil(n / 3)です。nが0なら0文字です。元サイズの端数によって増え方が変わるため、小さい値で「常にぴったり4/3倍」と計算しません。
表は横にスクロールできます
| 元バイト数 | Base64の文字数 | 末尾のパディング数 |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 4 | 2 |
| 2 | 4 | 1 |
| 3 | 4 | 0 |
| 4 | 8 | 2 |
例えば1バイトのASCII文字fはZg==、2バイトのfoはZm8=、3バイトのfooはZm9vになります。これらはRFC 4648のテストベクトルでも確認できます。RFC 4648:Test Vectors(新しいタブで開く)
この文字数は、今回のBase64部分だけの長さです。JSONのキー、引用符、Data URLの接頭辞、HTTPヘッダー、改行などは含みません。アップロードや保存の制限を調べるときは、どの段階のサイズを制限しているかを確認します。
標準Base64とURL-safeを混ぜない
説明用のバイト列fb ffは、文章ではない2バイトです。標準Base64では+/8=、URL-safeでは-_8=になります。URL-safeは標準のプラスとスラッシュを、それぞれハイフンとアンダースコアへ置き換えた文字集合を使います。この例ではパディングも残します。
URL-safeという名前でも、すべての受け取り先がパディング付き・なしの両方を受け付けるとは限りません。送信先が指定する形式を先に決め、見た目だけで標準とURL-safeを自動判別する前提にしません。
fb ffをBase64から元へ戻せても、そのバイト列をUTF-8として読むとこの例ではエラーになります。Base64の復元成功と、文字コードとしての解釈成功は別の確認です。画像や圧縮データなどのバイナリを、必ず文章としてdecodeする設計にはしません。
厳格に受け取る範囲を、例の契約として決める
次は、この説明用に「標準Base64、必要なパディングあり、空白・改行なし、同じバイト列を再符号化した表記と完全一致」という契約で読む関数です。空の入力は空バイト列として受け付けます。
import base64
def decode_canonical_standard(value):
if not isinstance(value, str):
raise ValueError("expected an ASCII string")
try:
value.encode("ascii")
raw = base64.b64decode(value, validate=True)
except (ValueError, UnicodeError) as exc:
raise ValueError("invalid standard Base64") from exc
if base64.b64encode(raw).decode("ascii") != value:
raise ValueError("noncanonical representation")
return raw
この契約では、不要な空白を勝手に削ったり、足りないパディングを補ったりしません。受け取り先が別形式を指定している場合は、その仕様に合う処理を別に作ります。
validate=Trueで文字集合やパディングを検査しても、すべての表記の正規性を確認したことにはしません。この例のPython 3.9環境では、Zh==もfのバイトへ戻りますが、再符号化結果はZg==なので、この関数では拒否します。意味のある入力バイトの後にあるパディング用ビットの違いを、表記比較で見つける例です。
この関数は、任意の巨大入力への資源制限や、認証の正しさを検証する実装ではありません。形式を満たした文字列を受け付けたことから、その内容が信頼できるとは判断しません。
AIには、変換ごとの型と検証範囲を返させる
説明用のデータをBase64へ変換するコードを作ってください。
入力は生の文字列で、文字コードはUTF-8です。
形式は標準Base64、必要なパディングあり、改行なしです。
出力:元文字列、UTF-8バイト列の16進表記、Base64、復元バイト列、照合結果。
文字数・元バイト数・Base64文字数を分けてください。
復元したバイト列を文字列へ戻す場合は、文字コードを明記してください。
勝手な正規化、改行追加、繰り返し復号はしないでください。
Base64を暗号化や認証確認と説明しないでください。
外部へ送信せず、実行した検査と未実施の検査を分けてください。
入力:[架空の短い文字列]。
今回は掲載コード、元バイト列の保持、サイズの式、標準とURL-safeの例、文章として読めないバイト列、例の受入契約をローカルで確認しました。実APIのフィールド仕様、Data URL、ファイル送信、秘密情報の保護を検証したものではありません。
参考資料
公式資料の確認日:2026年10月20日。掲載コードはローカルのPython 3.9環境で検査しました。最新版Pythonだけの追加引数は使っていません。外部送信、認証、AIサービス、画面表示は未検証です。
