メインコンテンツまでスキップ

エンコーディング基礎

所要時間: 45分


はじめに

「エンコーディング」という言葉は様々な場面で使われますが、本質的にはデータをある形式から別の形式に変換することです。

認証・認可の世界では、以下のエンコーディングが頻繁に登場します:

エンコーディング用途例
Base64JWT、Basic認証、バイナリデータ送信
Base64URLJWT(URL安全な形式)
URLエンコーディングredirect_uri、クエリパラメータ
文字エンコーディング(UTF-8)ユーザー入力、多言語対応
Hex(16進数)ハッシュ値、デバッグ出力

1. Base64

Base64とは

バイナリデータをテキスト(ASCII文字)で表現するエンコーディング方式です。

┌─────────────────────────────────────────────────────────────────────┐
│ Base64 の目的 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 問題: バイナリデータ(画像、暗号文など)をテキストで送りたい │
│ │
│ 例: メールはテキストプロトコル(7bit ASCII) │
│ → バイナリをそのまま送ると文字化けや破損 │
│ │
│ 解決: バイナリを「安全な64文字」で表現 │
│ A-Z, a-z, 0-9, +, / (と padding の =) │
│ │
└─────────────────────────────────────────────────────────────────────┘

エンコード方法

┌─────────────────────────────────────────────────────────────────────┐
│ なぜ「Base64」という名前なのか │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Base64 = 64種類の文字を使うエンコーディング │
│ │
│ 6ビットで表現できる値: 2^6 = 64 種類(0〜63) │
│ │
│ 64文字 = A-Z (26) + a-z (26) + 0-9 (10) + 記号2つ (+, /) │
│ = 26 + 26 + 10 + 2 = 64 │
│ │
│ → 6ビット = 1文字 という対応関係 │
│ → 全て印刷可能なASCII文字で安全に転送できる │
│ │
│ ※ 同様に Base32(5ビット = 32文字)、Base16(4ビット = 16文字) │
│ なども存在する │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 3バイト → 4文字 の理由 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 入力: 3バイト = 24ビット │
│ 出力: 24ビット ÷ 6ビット = 4文字 │
│ │
│ ┌────────┬────────┬────────┐ │
│ │ 8ビット │ 8ビット │ 8ビット │ ← 入力 3バイト │
│ └────────┴────────┴────────┘ │
│ ↓ ↓ ↓ │
│ ┌──────┬──────┬──────┬──────┐ │
│ │6ビット│6ビット│6ビット│6ビット│ ← 出力 4文字 │
│ └──────┴──────┴──────┴──────┘ │
│ │
│ データサイズ: 約33%増加(3バイト → 4バイト) │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Base64 エンコードの仕組み │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 入力データを 3バイト(24ビット)ずつに分割 │
│ │
│ 2. 24ビットを 6ビット × 4 に分割 │
│ │
│ 3. 各6ビット(0-63)を対応する文字に変換 │
│ │
│ 例: "Man" をエンコード │
│ │
│ M a n │
│ 01001101 01100001 01101110 (ASCII バイト値) │
│ ├──────┼┼──────┼┼──────┼┤ │
│ 010011 010110 000101 101110 (6ビットずつ分割) │
│ 19 22 5 46 (10進数) │
│ T W F u (Base64文字) │
│ │
│ 結果: "Man" → "TWFu" │
│ │
└─────────────────────────────────────────────────────────────────────┘

Base64文字テーブル

値:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
文字: A B C D E F G H I J K L M N O P

値: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
文字: Q R S T U V W X Y Z a b c d e f

値: 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
文字: g h i j k l m n o p q r s t u v

値: 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
文字: w x y z 0 1 2 3 4 5 6 7 8 9 + /

パディング(=)

入力が3バイトの倍数でない場合、= でパディングします。

┌─────────────────────────────────────────────────────────────────────┐
│ パディングの例 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 入力: "Ma"(2バイト = 16ビット) │
│ │
│ M a (不足) │
│ 01001101 01100001 00000000 ← 0で埋める │
│ │
│ 010011 010110 000100 (不完全) │
│ T W E = ← 1文字分不完全なので = 1つ │
│ │
│ 結果: "Ma" → "TWE=" │
│ │
│ --- │
│ │
│ 入力: "M"(1バイト = 8ビット) │
│ │
│ 結果: "M" → "TQ==" ← 2文字分不完全なので = 2つ │
│ │
└─────────────────────────────────────────────────────────────────────┘

実践例

# エンコード
$ echo -n "Hello, World!" | base64
SGVsbG8sIFdvcmxkIQ==

# デコード
$ echo "SGVsbG8sIFdvcmxkIQ==" | base64 -d
Hello, World!
// Java
import java.util.Base64;

// エンコード
String encoded = Base64.getEncoder().encodeToString("Hello".getBytes());
// "SGVsbG8="

// デコード
byte[] decoded = Base64.getDecoder().decode("SGVsbG8=");
String text = new String(decoded); // "Hello"

認証・認可での使用例

┌─────────────────────────────────────────────────────────────────────┐
│ Base64 の使用例 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Basic認証 │
│ Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ= │
│ └─ "username:password" の Base64 │
│ │
│ 2. JWT(後述のBase64URLを使用) │
│ eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIn0.xxxxx │
│ └─ ヘッダー └─ ペイロード │
│ │
│ 3. client_secret_basic │
│ Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ= │
│ └─ "client_id:client_secret" の Base64 │
│ │
│ 4. PKCE code_verifier から code_challenge 生成 │
│ code_challenge = BASE64URL(SHA256(code_verifier)) │
│ │
└─────────────────────────────────────────────────────────────────────┘

2. Base64URL

標準Base64の問題

標準のBase64には +/ が含まれますが、これらはURLで特別な意味を持ちます。

+  → URLでは空白(スペース)として解釈される
/ → URLではパス区切りとして解釈される
= → URLでは特別な意味を持つ可能性

Base64URL の違い

┌─────────────────────────────────────────────────────────────────────┐
│ Base64 vs Base64URL │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 標準 Base64: A-Z, a-z, 0-9, +, / (パディング: =) │
│ Base64URL: A-Z, a-z, 0-9, -, _ (パディング: 省略可) │
│ │
│ 変換ルール: │
│ + → - │
│ / → _ │
│ = → 削除(または保持) │
│ │
│ 例: │
│ 標準: "abc+/==" │
│ URL安全: "abc-_" │
│ │
└─────────────────────────────────────────────────────────────────────┘

JWTでの使用

JWTはBase64URL(パディングなし)を使用します。

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

↓ デコード

{"alg":"RS256","typ":"JWT"} ← ヘッダー
{"sub":"1234567890","name":"John"} ← ペイロード
(署名)
// Java でのBase64URL
import java.util.Base64;

// エンコード(パディングなし)
String encoded = Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(data);

// デコード
byte[] decoded = Base64.getUrlDecoder().decode(encoded);

パディング不一致問題

パディング(=)の有無でデコードが失敗するケースがあります。

┌─────────────────────────────────────────────────────────────────────┐
│ パディング不一致の問題 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【問題1】JWTをパディングありでデコードしようとする │
│ │
│ JWT のペイロード: eyJzdWIiOiIxMjM0NTY3ODkwIn0 │
│ │
│ ❌ 厳格なデコーダー(パディング必須) │
│ → "Invalid Base64 padding" エラー │
│ │
│ ✅ 対策: パディングを補完してからデコード │
│ eyJzdWIiOiIxMjM0NTY3ODkwIn0 → eyJzdWIiOiIxMjM0NTY3ODkwIn0= │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【問題2】ライブラリ間の挙動の違い │
│ │
│ ライブラリA: パディングなしで出力 │
│ ライブラリB: パディングありを期待 │
│ │
│ 例: PKCEの code_challenge │
│ サーバーA: "abc123def456" (パディングなし) │
│ サーバーB: "abc123def456==" (パディングあり) │
│ → 比較時に不一致! │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【問題3】データベース保存時の切り詰め │
│ │
│ 保存時: "SGVsbG8gV29ybGQ=" │
│ 取得時: "SGVsbG8gV29ybGQ" ← 末尾の = が消えている │
│ → デコード失敗または不正なデータ │
│ │
└─────────────────────────────────────────────────────────────────────┘

パディング補完の実装

// パディングを補完してデコードする
public static byte[] decodeBase64UrlSafe(String input) {
// パディングを補完
int padding = (4 - input.length() % 4) % 4;
String padded = input + "=".repeat(padding);

return Base64.getUrlDecoder().decode(padded);
}

// 使用例
String jwtPayload = "eyJzdWIiOiIxMjM0NTY3ODkwIn0"; // パディングなし
byte[] decoded = decodeBase64UrlSafe(jwtPayload); // 正常にデコード
┌─────────────────────────────────────────────────────────────────────┐
│ パディング補完のルール │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Base64文字列の長さ 必要なパディング │
│ ──────────────────────────────────── │
│ 4の倍数 0個(= 不要) │
│ 4の倍数 + 1 不正(ありえない) │
│ 4の倍数 + 2 2個(==) │
│ 4の倍数 + 3 1個(=) │
│ │
│ 計算式: (4 - length % 4) % 4 │
│ │
└─────────────────────────────────────────────────────────────────────┘

ベストプラクティス

┌─────────────────────────────────────────────────────────────────────┐
│ パディング問題の回避策 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. JWT/JOSE は常にパディングなしで扱う(RFC 7515準拠) │
│ → デコード時にパディングを補完する実装を使う │
│ │
│ 2. 比較時はパディングを正規化 │
│ → 両方からパディングを除去してから比較 │
│ → または両方にパディングを付与してから比較 │
│ │
│ 3. データベース保存時の注意 │
│ → VARCHAR ではなく TEXT を使う(長さ制限の問題回避) │
│ → バイナリとして保存する場合は BLOB を使う │
│ │
│ 4. ライブラリの挙動を確認 │
│ → エンコード時: withoutPadding() を明示 │
│ → デコード時: パディング補完対応のデコーダーを使用 │
│ │
└─────────────────────────────────────────────────────────────────────┘

3. URLエンコーディング(パーセントエンコーディング)

なぜ必要か

URLには使用できない文字や、特別な意味を持つ文字があります。

┌─────────────────────────────────────────────────────────────────────┐
│ URL で問題になる文字 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 予約文字(特別な意味を持つ): │
│ : / ? # [ ] @ ! $ & ' ( ) * + , ; = │
│ │
│ 例: │
│ ? → クエリ文字列の開始 │
│ & → パラメータの区切り │
│ = → キーと値の区切り │
│ # → フラグメントの開始 │
│ │
│ 非ASCII文字: │
│ 日本語、絵文字など │
│ │
└─────────────────────────────────────────────────────────────────────┘

エンコード方法

┌─────────────────────────────────────────────────────────────────────┐
│ URLエンコードの仕組み │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 文字をUTF-8バイト列に変換 │
│ 2. 各バイトを %XX(16進数)形式に変換 │
│ │
│ 例: スペース │
│ " " → UTF-8: 0x20 → %20 │
│ │
│ 例: 日本語「あ」 │
│ "あ" → UTF-8: 0xE3 0x81 0x82 → %E3%81%82 │
│ │
│ 例: 特殊文字 │
│ "&" → %26 │
│ "=" → %3D │
│ "?" → %3F │
│ "/" → %2F │
│ │
└─────────────────────────────────────────────────────────────────────┘

実践例

# Python でエンコード
$ python3 -c "import urllib.parse; print(urllib.parse.quote('hello world'))"
hello%20world

$ python3 -c "import urllib.parse; print(urllib.parse.quote('こんにちは'))"
%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF
// Java
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

// エンコード
String encoded = URLEncoder.encode("hello world", StandardCharsets.UTF_8);
// "hello+world" (注: スペースは + になる)

// デコード
String decoded = URLDecoder.decode("hello%20world", StandardCharsets.UTF_8);
// "hello world"

OAuth/OIDCでの重要性

┌─────────────────────────────────────────────────────────────────────┐
│ OAuth での URLエンコーディング │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 認可リクエスト例: │
│ │
│ /authorize? │
│ response_type=code& │
│ client_id=my_client& │
│ redirect_uri=https%3A%2F%2Fexample.com%2Fcallback& │
│ scope=openid%20profile%20email& │
│ state=abc123 │
│ │
│ 注意点: │
│ - redirect_uri は必ずエンコード(: / をエンコード) │
│ - scope のスペースは %20 または + に │
│ - state はそのまま(英数字のみなら) │
│ │
│ よくあるミス: │
│ ❌ redirect_uri=https://example.com/callback │
│ ✅ redirect_uri=https%3A%2F%2Fexample.com%2Fcallback │
│ │
└─────────────────────────────────────────────────────────────────────┘

application/x-www-form-urlencoded

POSTリクエストのボディでよく使われる形式です。

┌─────────────────────────────────────────────────────────────────────┐
│ application/x-www-form-urlencoded │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ トークンリクエスト例: │
│ │
│ POST /token HTTP/1.1 │
│ Content-Type: application/x-www-form-urlencoded │
│ │
│ grant_type=authorization_code& │
│ code=AUTH_CODE_HERE& │
│ redirect_uri=https%3A%2F%2Fexample.com%2Fcallback& │
│ client_id=my_client │
│ │
│ ルール: │
│ - key=value 形式 │
│ - & で区切り │
│ - スペースは + に変換(%20 も可) │
│ - 特殊文字は %XX 形式 │
│ │
└─────────────────────────────────────────────────────────────────────┘

4. 文字エンコーディング(UTF-8)

文字エンコーディングとは

文字をバイト列に変換する規則です。

┌─────────────────────────────────────────────────────────────────────┐
│ 文字エンコーディングの歴史 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ASCII(1963年) │
│ - 7ビット(128文字) │
│ - 英数字、記号のみ │
│ - 日本語など非対応 │
│ │
│ 各国独自エンコーディング │
│ - Shift_JIS(日本) │
│ - EUC-JP(日本/Unix) │
│ - GB2312(中国) │
│ - EUC-KR(韓国) │
│ → 互換性がなく、文字化けの原因に │
│ │
│ Unicode + UTF-8(現在の標準) │
│ - 世界中の文字を統一的に扱える │
│ - Webの標準エンコーディング │
│ │
└─────────────────────────────────────────────────────────────────────┘

UTF-8の仕組み

┌─────────────────────────────────────────────────────────────────────┐
│ UTF-8 エンコーディング │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 可変長エンコーディング(1〜4バイト) │
│ │
│ Unicodeコードポイント UTF-8バイト列 │
│ ───────────────────────────────────────────── │
│ U+0000 - U+007F (ASCII) 1バイト: 0xxxxxxx │
│ U+0080 - U+07FF 2バイト: 110xxxxx 10xxxxxx │
│ U+0800 - U+FFFF 3バイト: 1110xxxx 10xxxxxx 10xxxxxx │
│ U+10000 - U+10FFFF 4バイト: 11110xxx ... │
│ │
│ 例: │
│ "A" (U+0041) → 0x41 (1バイト) │
│ "あ" (U+3042) → 0xE3 0x81 0x82 (3バイト) │
│ "𠮷" (U+20BB7) → 0xF0 0xA0 0xAE 0xB7 (4バイト) │
│ │
└─────────────────────────────────────────────────────────────────────┘

UTF-8の特徴

┌─────────────────────────────────────────────────────────────────────┐
│ UTF-8 の利点 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. ASCII互換 │
│ 英数字は1バイト、既存のASCIIデータと互換 │
│ │
│ 2. 自己同期 │
│ 途中からでもバイト境界を特定できる │
│ (先頭バイトと継続バイトを区別可能) │
│ │
│ 3. バイトオーダー非依存 │
│ UTF-16のようなBOM問題がない │
│ │
│ 4. NULLを含まない │
│ C言語の文字列関数と互換 │
│ │
└─────────────────────────────────────────────────────────────────────┘

認証システムでの注意点

┌─────────────────────────────────────────────────────────────────────┐
│ 認証での文字エンコーディング │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. ユーザー名・パスワード │
│ - UTF-8で統一 │
│ - 正規化(NFC/NFD)に注意 │
│ │
│ 例: "café" の表現 │
│ NFC: "café" (4文字: c, a, f, é) │
│ NFD: "café" (5文字: c, a, f, e, ́ ) │
│ → 見た目は同じでもバイト列が異なる! │
│ │
│ 2. JWTクレーム │
│ - JSON は UTF-8 │
│ - 日本語の名前なども問題なく格納可能 │
│ │
│ 3. HTTPヘッダー │
│ - 基本的にASCIIのみ │
│ - 非ASCIIはエンコードが必要 │
│ (RFC 8187、または Base64) │
│ │
│ 4. データベース │
│ - 接続時のエンコーディング指定 │
│ - PostgreSQL: SET client_encoding = 'UTF8' │
│ │
└─────────────────────────────────────────────────────────────────────┘

5. 16進数(Hex)

16進数とは

0-9, A-F の16文字でバイナリを表現する方法です。

┌─────────────────────────────────────────────────────────────────────┐
│ 16進数の基本 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 10進数 2進数 16進数 │
│ ────────────────────────── │
│ 0 0000 0 │
│ 1 0001 1 │
│ ... │
│ 9 1001 9 │
│ 10 1010 A │
│ 11 1011 B │
│ 12 1100 C │
│ 13 1101 D │
│ 14 1110 E │
│ 15 1111 F │
│ │
│ 1バイト(8ビット)= 16進数2桁 │
│ 例: 255 = 11111111 = FF │
│ │
└─────────────────────────────────────────────────────────────────────┘

認証での使用例

┌─────────────────────────────────────────────────────────────────────┐
│ 16進数の使用例 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. ハッシュ値の表現 │
│ │
│ SHA-256 出力(32バイト = 64桁の16進数): │
│ a591a6d40bf420404a011733cfb7b190 │
│ d62c65bf0bcda32b57b277d9ad9f146e │
│ │
│ 2. 秘密鍵・公開鍵のダンプ │
│ │
│ 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01 │
│ 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01 │
│ │
│ 3. UUIDの表現 │
│ │
│ 550e8400-e29b-41d4-a716-446655440000 │
│ └─────────────────────────────────┘ │
│ 16進数(ハイフン区切り) │
│ │
│ 4. MACアドレス │
│ │
│ 00:1A:2B:3C:4D:5E │
│ │
│ 5. カラーコード │
│ │
│ #FF5733 = RGB(255, 87, 51) │
│ │
└─────────────────────────────────────────────────────────────────────┘

実践例

# 文字列を16進数に変換
$ echo -n "Hello" | xxd -p
48656c6c6f

# 16進数を文字列に変換
$ echo "48656c6c6f" | xxd -r -p
Hello

# SHA-256ハッシュ(16進数出力)
$ echo -n "password" | shasum -a 256
5e884898da28047d9d94f6c2d77e3b64...
// Java
import java.util.HexFormat;

// バイト配列 → 16進数文字列
byte[] bytes = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
String hex = HexFormat.of().formatHex(bytes);
// "48656c6c6f"

// 16進数文字列 → バイト配列
byte[] decoded = HexFormat.of().parseHex("48656c6c6f");

エンコーディングの組み合わせ

実際の認証フローでは、複数のエンコーディングが組み合わされます。

例: Basic認証

┌─────────────────────────────────────────────────────────────────────┐
│ Basic認証のエンコーディング │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 入力: username = "user", password = "パスワード" │
│ │
│ Step 1: UTF-8エンコード │
│ "user:パスワード" → UTF-8バイト列 │
│ │
│ Step 2: Base64エンコード │
│ UTF-8バイト列 → "dXNlcjrjg5Hjgrnjg6/jg7zjg4k=" │
│ │
│ Step 3: HTTPヘッダーに設定 │
│ Authorization: Basic dXNlcjrjg5Hjgrnjg6/jg7zjg4k= │
│ │
└─────────────────────────────────────────────────────────────────────┘

例: OAuth redirect_uri

┌─────────────────────────────────────────────────────────────────────┐
│ redirect_uri のエンコーディング │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 元のURL: │
│ https://example.com/callback?extra=value&foo=bar │
│ │
│ URLエンコード後: │
│ https%3A%2F%2Fexample.com%2Fcallback%3Fextra%3Dvalue%26foo%3Dbar │
│ │
│ 認可リクエストに含める: │
│ /authorize? │
│ client_id=xxx& │
│ redirect_uri=https%3A%2F%2Fexample.com%2Fcallback%3Fextra%3D... │
│ │
│ 注意: redirect_uri 内の ? や & もエンコードする │
│ │
└─────────────────────────────────────────────────────────────────────┘

よくある間違いとトラブルシューティング

1. 二重エンコード

┌─────────────────────────────────────────────────────────────────────┐
│ 二重エンコード問題 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ❌ 間違い: すでにエンコードされた文字列を再度エンコード │
│ │
│ 元: "hello world" │
│ 1回目: "hello%20world" │
│ 2回目: "hello%2520world" ← %が%25にエンコードされた │
│ │
│ デコード時: │
│ 1回目: "hello%20world" ← まだエンコードされている! │
│ │
│ ✅ 対策: エンコード/デコードの回数を揃える │
│ │
└─────────────────────────────────────────────────────────────────────┘

2. Base64 vs Base64URL の混同

┌─────────────────────────────────────────────────────────────────────┐
│ Base64/Base64URL 混同問題 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ JWT を標準Base64でデコードしようとする │
│ │
│ ❌ Base64.getDecoder().decode(jwtPart) │
│ → "-" や "_" が含まれているとエラー │
│ │
│ ✅ Base64.getUrlDecoder().decode(jwtPart) │
│ → 正しくデコードできる │
│ │
└─────────────────────────────────────────────────────────────────────┘

3. 文字エンコーディングの不一致

┌─────────────────────────────────────────────────────────────────────┐
│ 文字エンコーディング不一致問題 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ サーバー: UTF-8でパスワードを保存 │
│ クライアント: Shift_JISで送信 │
│ │
│ "パスワード" のバイト列: │
│ UTF-8: E3 83 91 E3 82 B9 E3 83 AF E3 83 BC E3 83 89 │
│ Shift_JIS: 83 70 83 58 83 8F 81 5B 83 68 │
│ │
│ → 全く異なるバイト列になり、認証失敗 │
│ │
│ ✅ 対策: 常にUTF-8を使用、Content-Typeで明示 │
│ Content-Type: application/json; charset=utf-8 │
│ │
└─────────────────────────────────────────────────────────────────────┘

まとめ

エンコーディング用途注意点
Base64バイナリ→テキスト変換+, /, = を含む
Base64URLJWT、URL内のバイナリ-, _ を使用、パディング省略可
URLエンコーディングURLパラメータ二重エンコードに注意
UTF-8文字→バイト変換現在の標準、常に明示する
Hexデバッグ、ハッシュ表現1バイト = 2文字
┌─────────────────────────────────────────────────────────────────────┐
│ 覚えておくべきポイント │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. エンコードとデコードは必ずペアで │
│ 2. エンコーディングの種類を混同しない │
│ 3. 文字エンコーディングは常にUTF-8 │
│ 4. 二重エンコード/デコードに注意 │
│ 5. JWTはBase64URL(標準Base64ではない) │
│ │
└─────────────────────────────────────────────────────────────────────┘

参考