Base64エンコーディング入門:データ変換の仕組みと活用法
Base64の基本原理から実装方法、セキュリティ考慮事項まで、データエンコーディングの完全ガイド。
i4u開発チーム
9分で読む
Base64とは何か
Base64は、バイナリデータを印刷可能なASCII文字だけで表現するエンコーディング方式です。メール添付ファイル、データURI、APIでのバイナリデータ送信など、様々な場面で使用されています。
Base64の仕組み
エンコーディングの原理
Base64は3バイト(24ビット)のデータを4文字(6ビット×4)に変換します:
// Base64エンコーディングの仕組みを可視化
function visualizeBase64Encoding(text) {
const bytes = new TextEncoder().encode(text);
const result = [];
for (let i = 0; i < bytes.length; i += 3) {
// 3バイトを取得(不足分は0で埋める)
const byte1 = bytes[i];
const byte2 = bytes[i + 1] || 0;
const byte3 = bytes[i + 2] || 0;
// 24ビットの数値に結合
const combined = (byte1 << 16) | (byte2 << 8) | byte3;
// 6ビットずつ4つに分割
const char1 = (combined >> 18) & 0x3F;
const char2 = (combined >> 12) & 0x3F;
const char3 = (combined >> 6) & 0x3F;
const char4 = combined & 0x3F;
result.push({
input: text.slice(i/3, i/3 + 1),
bytes: [byte1, byte2, byte3],
binary: combined.toString(2).padStart(24, '0'),
indices: [char1, char2, char3, char4],
output: btoa(text.slice(i/3, i/3 + 1))
});
}
return result;
}
Base64文字セット
const base64Chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// URLセーフなBase64(Base64url)
const base64UrlChars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
実装方法
JavaScript での実装
// カスタムBase64エンコーダー
class Base64Encoder {
constructor() {
this.chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
}
encode(input) {
if (typeof input === 'string') {
input = new TextEncoder().encode(input);
}
let result = '';
let i = 0;
while (i < input.length) {
const a = input[i++];
const b = i < input.length ? input[i++] : 0;
const c = i < input.length ? input[i++] : 0;
const bitmap = (a << 16) | (b << 8) | c;
result += this.chars[(bitmap >> 18) & 63];
result += this.chars[(bitmap >> 12) & 63];
result += i - 2 < input.length ? this.chars[(bitmap >> 6) & 63] : '=';
result += i - 1 < input.length ? this.chars[bitmap & 63] : '=';
}
return result;
}
decode(encoded) {
// パディング文字を除去
encoded = encoded.replace(/=/g, '');
const bytes = [];
let i = 0;
while (i < encoded.length) {
const encoded1 = this.chars.indexOf(encoded[i++]);
const encoded2 = this.chars.indexOf(encoded[i++]);
const encoded3 = i < encoded.length ? this.chars.indexOf(encoded[i++]) : 64;
const encoded4 = i < encoded.length ? this.chars.indexOf(encoded[i++]) : 64;
const bitmap = (encoded1 << 18) | (encoded2 << 12) |
(encoded3 << 6) | encoded4;
bytes.push((bitmap >> 16) & 255);
if (encoded3 !== 64) bytes.push((bitmap >> 8) & 255);
if (encoded4 !== 64) bytes.push(bitmap & 255);
}
return new Uint8Array(bytes);
}
}
Python での実装
import base64
from typing import Union
class Base64Handler:
@staticmethod
def encode_text(text: str, encoding: str = 'utf-8') -> str:
"""テキストをBase64エンコード"""
bytes_data = text.encode(encoding)
return base64.b64encode(bytes_data).decode('ascii')
@staticmethod
def decode_text(encoded: str, encoding: str = 'utf-8') -> str:
"""Base64をテキストにデコード"""
bytes_data = base64.b64decode(encoded)
return bytes_data.decode(encoding)
@staticmethod
def encode_file(file_path: str) -> str:
"""ファイルをBase64エンコード"""
with open(file_path, 'rb') as file:
return base64.b64encode(file.read()).decode('ascii')
@staticmethod
def decode_to_file(encoded: str, output_path: str) -> None:
"""Base64をファイルにデコード"""
bytes_data = base64.b64decode(encoded)
with open(output_path, 'wb') as file:
file.write(bytes_data)
@staticmethod
def url_safe_encode(data: bytes) -> str:
"""URLセーフなBase64エンコード"""
return base64.urlsafe_b64encode(data).decode('ascii')
実用的な使用例
画像のData URI化
// 画像をBase64 Data URIに変換
async function imageToDataURI(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const base64 = reader.result;
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// Canvas要素からBase64画像を生成
function canvasToBase64(canvas, type = 'image/png', quality = 0.92) {
return canvas.toDataURL(type, quality);
}
// Base64画像のサイズ最適化
function optimizeBase64Image(dataURI, maxWidth, maxHeight) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// アスペクト比を維持してリサイズ
let width = img.width;
let height = img.height;
if (width > maxWidth || height > maxHeight) {
const ratio = Math.min(maxWidth / width, maxHeight / height);
width *= ratio;
height *= ratio;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
resolve(canvas.toDataURL('image/jpeg', 0.85));
};
img.src = dataURI;
});
}
JWT トークンの処理
interface JWTPayload {
sub: string;
exp: number;
iat: number;
[key: string]: any;
}
class JWTHandler {
// Base64urlエンコード
private base64UrlEncode(str: string): string {
return btoa(str)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
// Base64urlデコード
private base64UrlDecode(str: string): string {
str += '='.repeat((4 - str.length % 4) % 4);
str = str.replace(/-/g, '+').replace(/_/g, '/');
return atob(str);
}
// JWTをデコード
decodeJWT(token: string): { header: any, payload: JWTPayload, signature: string } {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT format');
}
return {
header: JSON.parse(this.base64UrlDecode(parts[0])),
payload: JSON.parse(this.base64UrlDecode(parts[1])),
signature: parts[2]
};
}
// JWTを作成(署名なし - デモ用)
createUnsignedJWT(payload: JWTPayload): string {
const header = {
alg: 'none',
typ: 'JWT'
};
const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
const encodedPayload = this.base64UrlEncode(JSON.stringify(payload));
return `${encodedHeader}.${encodedPayload}.`;
}
}
メール添付ファイルの処理
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
def attach_file_as_base64(msg, file_path, filename):
"""ファイルをBase64エンコードしてメールに添付"""
with open(file_path, 'rb') as file:
part = MIMEBase('application', 'octet-stream')
part.set_payload(file.read())
# Base64エンコード
encoders.encode_base64(part)
part.add_header(
'Content-Disposition',
f'attachment; filename= {filename}'
)
msg.attach(part)
return msg
パフォーマンス最適化
大容量データの処理
// ストリーミングBase64エンコーダー
class StreamingBase64Encoder {
constructor() {
this.buffer = '';
this.chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
}
async* encodeStream(stream) {
const reader = stream.getReader();
let buffer = new Uint8Array(0);
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
if (buffer.length > 0) {
yield this.encodeChunk(buffer, true);
}
break;
}
// バッファに追加
const newBuffer = new Uint8Array(buffer.length + value.length);
newBuffer.set(buffer);
newBuffer.set(value, buffer.length);
buffer = newBuffer;
// 3の倍数バイトごとに処理
const chunkSize = Math.floor(buffer.length / 3) * 3;
if (chunkSize > 0) {
yield this.encodeChunk(buffer.slice(0, chunkSize), false);
buffer = buffer.slice(chunkSize);
}
}
} finally {
reader.releaseLock();
}
}
encodeChunk(bytes, isLast) {
let result = '';
for (let i = 0; i < bytes.length; i += 3) {
const remaining = bytes.length - i;
const a = bytes[i];
const b = remaining > 1 ? bytes[i + 1] : 0;
const c = remaining > 2 ? bytes[i + 2] : 0;
const bitmap = (a << 16) | (b << 8) | c;
result += this.chars[(bitmap >> 18) & 63];
result += this.chars[(bitmap >> 12) & 63];
if (remaining > 1) {
result += this.chars[(bitmap >> 6) & 63];
} else if (isLast) {
result += '=';
}
if (remaining > 2) {
result += this.chars[bitmap & 63];
} else if (isLast) {
result += '=';
}
}
return result;
}
}
セキュリティ考慮事項
Base64の誤解と正しい使い方
// ❌ 間違った使用例:パスワードの「暗号化」
const encryptPassword = (password) => {
return btoa(password); // これは暗号化ではない!
};
// ✅ 正しい使用例:バイナリデータの転送
const transferBinaryData = async (file) => {
const arrayBuffer = await file.arrayBuffer();
const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
// APIに送信
return fetch('/api/upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: file.name,
data: base64,
// 適切な認証とCSRFトークンを含める
csrf: getCSRFToken()
})
});
};
データサイズの増加への対処
Base64エンコーディングは元データの約133%のサイズになります:
// サイズ計算ユーティリティ
function calculateBase64Size(originalSize) {
// 4/3倍 + パディング
const encodedSize = Math.ceil(originalSize / 3) * 4;
return {
original: originalSize,
encoded: encodedSize,
increase: ((encodedSize / originalSize - 1) * 100).toFixed(1) + '%',
recommendation: originalSize > 1048576
? '1MB以上のデータは直接バイナリ転送を推奨'
: 'Base64での転送可能'
};
}
よくある使用場面
1. インラインCSS/JavaScriptでの画像埋め込み
.icon {
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PC9zdmc+');
}
2. APIでのファイルアップロード
const uploadFile = async (file) => {
const base64 = await fileToBase64(file);
return fetch('/api/files', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: file.name,
type: file.type,
content: base64
})
});
};
3. LocalStorageへのバイナリデータ保存
// 画像をLocalStorageに保存
function saveImageToStorage(key, imageFile) {
const reader = new FileReader();
reader.onload = (e) => {
localStorage.setItem(key, e.target.result);
};
reader.readAsDataURL(imageFile);
}
// LocalStorageから画像を復元
function loadImageFromStorage(key) {
const dataURI = localStorage.getItem(key);
if (dataURI) {
const img = new Image();
img.src = dataURI;
return img;
}
return null;
}
ツールとリソース
便利なオンラインツール
- i4u Base64エンコーダー: テキストとファイルのエンコード
- i4u Base64デコーダー: Base64データのデコード
- i4u 画像Base64変換: 画像のData URI生成
まとめ
Base64は、バイナリデータをテキスト形式で安全に転送するための重要な技術です。適切に使用することで、データの互換性と転送性を向上させることができます。
ただし、Base64は暗号化ではないこと、データサイズが増加することを理解し、用途に応じて適切に選択することが重要です。