シンプルなオンラインツール

開発

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;
}

ツールとリソース

便利なオンラインツール

まとめ

Base64は、バイナリデータをテキスト形式で安全に転送するための重要な技術です。適切に使用することで、データの互換性と転送性を向上させることができます。

ただし、Base64は暗号化ではないこと、データサイズが増加することを理解し、用途に応じて適切に選択することが重要です。