0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JWT を黒魔術から「読める文字列」にする

0
Last updated at Posted at 2026-01-01

※ 本記事に記載されているトークンは学習用のダミーデータであり、実在サービスや秘密情報とは一切関係ありません

🤔 この文字列は何を表しているでしょう?

突然ですが、次の文字列を見てください。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo5OTk5OTk5OTk5OTk5OTk5LCJyb2xlIjoiaG9nZWVlZWVlZWUiLCJ0ZXN0IjoidGVzdCIsImV4cCI6MX0.MgUiFOhKyBSLbAvLv_xSUiqhN1bxSmQhi_iNe8pVz3M

黒魔術でしょうか、いいえ、JWT(JSON Web Token)と呼ばれる認証・認可に利用されるトークンです。


❓ 問題

この JWT は 何を表してるでしょうか?

  • 誰?
  • どんな権限?
  • 本当に信頼できる?

この記事では、JWT の仕組みを Python コードとともに解析しながら 解説していきます。


🔍 JWT とは何か(超ざっくり)

JWT(JSON Web Token)は一言で言うと、

改ざん検知付きの自己紹介カード

です。

  • 中身はただの JSON
  • 暗号化されていない(誰でも読める)
  • でも 勝手に書き換えるとバレる

🧩 JWT の構造

JWT は . で区切られた 3 つのパーツで構成されます。

header.payload.signature

🧪 やってみよう①:JWT の中身を読む

header と payload は Base64URL でエンコードされているだけなので、decode すれば普通に読めます。

import base64
import json

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo5OTk5OTk5OTk5OTk5OTk5LCJyb2xlIjoiaG9nZWVlZWVlZWUiLCJ0ZXN0IjoidGVzdCIsImV4cCI6MX0.MgUiFOhKyBSLbAvLv_xSUiqhN1bxSmQhi_iNe8pVz3M"

def b64url_decode(data: str) -> bytes:
    padding = "=" * (-len(data) % 4)
    return base64.urlsafe_b64decode(data + padding)

header_b64, payload_b64, _ = token.split(".")

header = json.loads(b64url_decode(header_b64))
payload = json.loads(b64url_decode(payload_b64))

print(header)
print(payload)

実行結果

header

{
  "alg": "HS256",
  "typ": "JWT"
}

payload

(各自で確認してみてください)

{
    "user_id": xxxxxxx,
    "role": "xxxxxx",
    .....
    "exp": xxxxxxxxxxx
}

🔐 JWT が改ざんできない理由(軽く暗号の話)

この JWT は header に "alg": "HS256" と記載されている通り、
HS256 という署名アルゴリズムで署名されています。

  • HMAC + SHA-256
  • 共通鍵方式(発行者と検証者が同じ秘密鍵を持つ)
  • header.payload 全体を 鍵付きハッシュ(HMAC) の入力にする

JWT では、

  1. headerpayload. で結合した文字列を作り
  2. それを UTF-8 で encode して byte 列に変換し
  3. 秘密鍵を使って HMAC-SHA256 を適用し
  4. 得られた byte 列を Base64URL で encode したものを signature としています

🧪 やってみよう②:署名を検証する

import hmac
import hashlib

def b64url_encode(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")

SECRET = b"my-secret-key"
signing_input = f"{header_b64}.{payload_b64}".encode("utf-8")

expected = hmac.new(
    SECRET,
    signing_input,
    hashlib.sha256
).digest()

print(b64url_encode(expected))

一致すれば正当な JWT、不一致なら偽物です。


🧠 JWT を一言で

JWT は
中身は誰でも読めるが、
正しい鍵を知らないと作り直せない自己紹介カード


✍️ おわりに

JWT はライブラリに任せがちですが、
一度自分で decode / verify してみると理解が一気に進みます。

※ 本記事の JWT トークンは 学習用のダミーデータであり、
実在サービスや秘密情報とは一切関係ありません。

解析コード例

import base64
import json
import hmac
import hashlib
from abc import ABC, abstractmethod

# =====================================================
# Base64URL decode / encode
# =====================================================
def b64url_decode(data: str) -> bytes:
    padding = "=" * (-len(data) % 4)
    return base64.urlsafe_b64decode(data + padding)

def b64url_encode(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")

# =====================================================
# Algorithm abstract class
# =====================================================
class Algorithm(ABC):
    @abstractmethod
    def verify(self, signing_input: bytes, signature: bytes) -> bool:
        pass

# =====================================================
# HS256
# =====================================================
class HS256(Algorithm):
    def __init__(self, secret: bytes):
        self.secret = secret

    def verify(self, signing_input: bytes, signature: bytes) -> bool:
        print("\n[HS256] --- signature analysis ---")
        print("secret:", self.secret)

        expected = hmac.new(
            self.secret,
            signing_input,
            hashlib.sha256
        ).digest()
        print("expected signature (b64url):", b64url_encode(expected))

        return hmac.compare_digest(expected, signature)

# =====================================================
# RS256(今回は実装しませんmm)
# =====================================================
class RS256(Algorithm):
    def __init__(self, public_key_pem: bytes):
        pass
    def verify(self, signing_input: bytes, signature: bytes) -> bool:
        pass

# =====================================================
# Algorithm Factory
# =====================================================
def algorithm_factory(alg: str) -> Algorithm:
    if alg == "HS256":
        return HS256(b"my-secret-key")

    # if alg == "RS256":
    #     pass

    raise ValueError(f"Unsupported alg: {alg}")

# =====================================================
# main
# =====================================================
if __name__ == "__main__":

    token = (
        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo5OTk5OTk5OTk5OTk5OTk5LCJyb2xlIjoiaG9nZWVlZWVlZWUiLCJ0ZXN0IjoidGVzdCIsImV4cCI6MX0.MgUiFOhKyBSLbAvLv_xSUiqhN1bxSmQhi_iNe8pVz3M"
    )
    print("JWT:", token)

    header_b64, payload_b64, signature_b64 = token.split(".")

    header = json.loads(b64url_decode(header_b64))
    payload = json.loads(b64url_decode(payload_b64))
    signature = b64url_decode(signature_b64)

    print("\n--- decoded header ---")
    print(json.dumps(header, indent=2))

    print("\n--- decoded payload ---")
    print(json.dumps(payload, indent=2))


    signing_input = f"{header_b64}.{payload_b64}".encode("utf-8")

    print("\n--- signing input ---")
    print("signing_input:", signing_input.decode())

    algorithm = algorithm_factory(header["alg"])
    valid = algorithm.verify(signing_input, signature)

    print("\n--- result ---")
    print("signature valid:", valid)


0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?