【入門編】 JWTの署名アルゴリズムRS256とHS256の使い分け – Web APIアーキテクチャ・データ連携実践ガイド

皆さん、こんにちは!ネットワークの深淵を愛するプロトコルスペシャリスト、〇〇(筆者の名前)です。Web APIやインフラの世界に足を踏み入れたばかりの皆さん、JWTって言葉を耳にして「うわ、なんか難しそう…」って思っていませんか? 大丈夫です、その気持ち、よ〜く分かります!

今回は、Web APIにおける「通行手形」とも言えるJWT(JSON Web Token)の、特に「署名(Signature)」という部分にスポットを当てて、その仕組みを一緒に紐解いていきましょう。特に、HS256 と RS256 という二つの署名アルゴリズムについて、一体どう使い分ければ良いのか、郵便配達や身近な例え話も交えながら、優しく丁寧に解説していきますね!

なぜJWTの署名が大切なの?

まず、「JWTって何だっけ?」という方のために、超ざっくりとおさらいです。JWTは、Web APIの世界で「あなたは誰ですか?」「この操作を許可します」といった情報を、安全にやり取りするためのデジタルな「身分証明書」や「通行手形」のようなものなんです。

JWTは主に3つの部分から構成されています。

1. Header(ヘッダー): JWTの種類や署名に使ったアルゴリズムなどの情報。
2. Payload(ペイロード): ユーザーIDや権限といった、伝えたい情報本体。
3. Signature(署名): これが今回の主役!ヘッダーとペイロードを基に生成される、JWTの「正当性」や「改ざんがないこと」を証明する部分です。

想像してみてください。あなたは大切な手紙を送ります。その手紙の封筒に、もし封蝋(シーリングワックス)や自分の印鑑が押されていなかったらどうでしょう? 途中で誰かに開けられて、中身を書き換えられてしまうかもしれませんよね。

JWTの「署名」は、まさにこの「封蝋」や「印鑑」のような役割を果たします。これがあるおかげで、受け取った側は「このJWTは、間違いなくあの人が発行したもので、途中で誰も改ざんしていないな」と信頼できるわけです。

さあ、この大切な署名を生成・検証する方法には、いくつか種類があります。その中でも、特に現場でよく使われるのが HS256 と RS256 というアルゴリズムなんです。一歩ずつ理解していきましょう!

—

署名アルゴリズムの二大巨頭!HS256 と RS256

HS256 と RS256。この二つの違いを理解する上で、まず「鍵(キー)」という概念が非常に重要になります。鍵というと、家の鍵や金庫の鍵を想像しますよね。まさにそれと同じような働きをします。

デジタルな世界では、この鍵を使って情報を「暗号化」したり、「署名」を生成したり、「署名を検証」したりするわけです。そして、この鍵の使い方が、HS256 と RS256 で大きく異なるんですよ。

具体的には、

  • HS256: 「対称鍵(Symmetric Key)」を使います。
  • RS256: 「非対称鍵(Asymmetric Key)」を使います。

なんだか難しそうな言葉が出てきましたね!でも安心してください。それぞれの鍵の考え方を、身近な例で見ていきましょう。

1. まずはシンプルに!「対称鍵」方式の HS256

HS256 は、「対称鍵暗号方式」と呼ばれる考え方に基づいています。これは、署名を生成する時も、その署名を検証する時も、全く同じ「一つの秘密の鍵」を使う方法です。

例えるなら、こんな感じです。

郵便ポストの「秘密の合言葉」

あなたが友人に手紙を送ります。その手紙が本物であることを証明するために、手紙の最後に「秘密の合言葉」を書いておきます。友人は、あなたが事前に教えておいた同じ「秘密の合言葉」が手紙に書かれていることを確認して、「これは〇〇が送った本物の手紙だ!」と判断します。

この「秘密の合言葉」が、HS256 で使う「対称鍵」にあたります。

HS256 の特徴

  • 仕組み: JWTの発行者と検証者が、同じ秘密の鍵(Shared Secret Key)を共有し、それを使って署名を生成・検証します。
  • メリット:
  • シンプルで高速: 鍵が一つなので、処理がシンプルで、署名の生成も検証も非常に高速です。
  • 実装が容易: 複雑な鍵管理が不要なため、開発者にとっては扱いやすいアルゴリズムです。
  • デメリット:
  • 鍵の共有が課題: JWTを発行する側と検証する側の両方が、同じ秘密の鍵を持っている必要があります。もしこの鍵が第三者に漏れてしまったら、JWTを改ざんされても気づけなくなってしまいます。複数のサービスで鍵を共有する場合、その「鍵の安全な共有方法」が大きな課題となります。
  • 信頼性の限界: 鍵を共有している相手が多ければ多いほど、どこかで鍵が漏洩するリスクが高まります。

どんな時に HS256 を使う?

  • JWTの発行元と検証元が同じアプリケーション内、または密接に連携している少数のサービス間の場合。
  • 例えば、あなたのWebサイトのユーザー認証で、ログインAPIがJWTを発行し、その後のAPIがそのJWTを検証する場合などです。この場合、鍵は同じシステム内で安全に管理しやすいですよね。
  • パフォーマンスが非常に重要で、かつ鍵の共有範囲が限定的で安全に管理できる環境。

HS256 のコード例(Python)

Pythonの PyJWT ライブラリを使って、HS256 でJWTを生成し、検証する簡単な例を見てみましょう。

import jwt
import datetime
import time

# HS256で使用する秘密鍵(Shared Secret Key)。
# 実際には環境変数などから安全に読み込むべきです。
SECRET_KEY = "your-very-secret-key-that-no-one-else-knows"

# --- JWTの生成 ---
def create_hs256_jwt(user_id: str):
    payload = {
        "user_id": user_id,
        "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1), # 有効期限を1時間に設定
        "iat": datetime.datetime.utcnow(),                              # 発行日時
    }
    # ヘッダーとペイロードを秘密鍵で署名し、JWTを生成
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    print(f"HS256 JWT 生成完了: {token}")
    return token

# --- JWTの検証 ---
def verify_hs256_jwt(token: str):
    try:
        # JWTを秘密鍵でデコード(検証)
        decoded_payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        print(f"HS256 JWT 検証成功!ペイロード: {decoded_payload}")
        return decoded_payload
    except jwt.ExpiredSignatureError:
        print("JWTの有効期限が切れています。")
        return None
    except jwt.InvalidTokenError:
        print("JWTが無効です(署名が不正または改ざんされています)。")
        return None

if __name__ == "__main__":
    # JWTを生成してみる
    my_jwt = create_hs256_jwt("alice123")

    # 生成したJWTを検証してみる
    if my_jwt:
        verify_hs256_jwt(my_jwt)

        print("\n--- 無効なJWTを試す(秘密鍵を間違える)---")
        try:
            # 異なる秘密鍵で検証しようとするとエラーになる
            jwt.decode(my_jwt, "wrong-secret-key", algorithms=["HS256"])
        except jwt.InvalidTokenError as e:
            print(f"秘密鍵が違うので検証失敗(想定通り): {e}")

        print("\n--- 無効なJWTを試す(改ざん)---")
        # JWTの一部を意図的に改ざんしてみる
        parts = my_jwt.split('.')
        # ペイロードの一部を書き換える(例:user_idを'bob456'に)
        # ※実際にはbase64デコードしてJSONを書き換え、再度base64エンコードする必要がありますが、ここでは簡単な例として末尾を書き換えるだけにします
        # 厳密な改ざんの例ではありませんが、署名が不正になることを示します
        tampered_jwt = f"{parts[0]}.eyJ1c2VyX2lkIjoiYm9iNDU2IiwgImV4cCI6MTk2NjY2Njc3NywgImlhdCI6MTY3NzgzMzM1Nn0.invalid-signature"
        verify_hs256_jwt(tampered_jwt) # 署名が不正で検証失敗するはず

このコードを見ると、「SECRET_KEY」という文字列が署名にも検証にも使われているのが分かりますよね。この鍵が外部に漏れないように厳重に管理することが、HS256 を使う上での最大のポイントになります。

2. 信頼の証!「非対称鍵」方式の RS256

次に、「非対称鍵暗号方式」を使う RS256 です。これは、署名を生成する時と、その署名を検証する時とで、異なる二つの鍵のペアを使う方法です。

例えるなら、こんな感じです。

郵便局の「私書箱」と「印鑑証明」

あなたが友人に「この手紙は私が送ったものです!」と証明したいとします。

1. あなたは、自分しか持っていない「秘密の印鑑」(秘密鍵)を使って、手紙の裏に印鑑を押します。
2. 同時に、あなたは「この印鑑の形はこれですよ」という「印鑑証明書」(公開鍵)を、郵便局や公衆の目に触れる場所に置いておきます。
3. 友人(検証者)は、手紙を受け取ったら、郵便局に置いてある「印鑑証明書」(公開鍵)と、手紙に押された印鑑の形が一致するかどうかを確認します。

もし一致すれば、「ああ、これは間違いなく〇〇が自分の印鑑(秘密鍵)で押したものだから、本物だ!」と信頼できるわけです。印鑑証明書(公開鍵)は誰でも見られますが、その印鑑と同じものを偽造することは非常に難しいですよね。

これが、RS256 で使う「非対称鍵」の考え方です。

RS256 の特徴

  • 仕組み:
  • JWTの発行者は、秘密鍵(Private Key)という、自分だけが持っている鍵で署名を生成します。
  • JWTの検証者は、発行者が公開している公開鍵(Public Key)を使って、その署名を検証します。
  • 公開鍵は誰にでも教えても大丈夫ですが、秘密鍵は絶対に誰にも教えてはいけません。
  • メリット:
  • 鍵の安全な配布: 秘密鍵は発行者だけが持ち続け、公開鍵だけを広く配布できるため、鍵の共有(配送)に関するセキュリティリスクが大幅に軽減されます。
  • 高い信頼性: 公開鍵は漏洩しても問題ないため、複数のサービスと連携する際に非常に強力な信頼基盤となります。
  • PKIとの連携: 公開鍵基盤(PKI: Public Key Infrastructure)という仕組みと連携することで、公開鍵自体の信頼性も保証できます。
  • デメリット:
  • 複雑な鍵管理: 秘密鍵と公開鍵のペアを生成・管理する必要があり、HS256 に比べて少し複雑になります。
  • 計算コスト: 署名の生成・検証に HS256 よりも多くの計算が必要になるため、処理速度は若干遅くなります。

公開鍵基盤(PKI)って何?

「PKI」という言葉が出てきましたね。これもちょっと専門的ですが、とても大切な仕組みです。

先ほどの例で言えば、「印鑑証明書」は誰でも見られると言いましたが、その「印鑑証明書」自体が本当に本物なのか?という疑問が湧きませんか?

PKIは、この「公開鍵が本当に信頼できるものか」を証明するための仕組みです。具体的には、「認証局(CA: Certificate Authority)」と呼ばれる第三者の機関が、公開鍵の持ち主が誰であるかを保証する「デジタル証明書」を発行します。

例えるなら、あなたのパスポートや運転免許証を発行する「政府機関」のようなものです。これにより、受け取った側は、その公開鍵が「認証局」という信頼できる機関によってお墨付きをもらっているから「本物の公開鍵だ!」と安心して使えるわけです。

どんな時に RS256 を使う?

  • 複数の独立したサービス(マイクロサービス、OAuth/OpenID Connectプロバイダとクライアントなど)間でJWTを共有する場合。
  • 例えば、GoogleやFacebookのような認証基盤(IdP: Identity Provider)が発行したJWTを、あなたのWebサービス(SP: Service Provider)が検証するようなケースです。IdPは秘密鍵を持ち、SPは公開鍵を使って検証します。
  • セキュリティ、特に「信頼性」が最重要視される場面。
  • 鍵の管理をよりセキュアに行いたい場合。

RS256 のコード例(Python)

RS256 でJWTを生成・検証するには、秘密鍵と公開鍵のペアが必要です。ここでは、簡略化のため、サンプル鍵を直接コードに記述しますが、実際には openssl コマンドなどで鍵ペアを生成し、ファイルとして管理します。

import jwt
import datetime
import time

# --- RSA秘密鍵と公開鍵(例として文字列で直接記述) ---
# 実際には、opensslなどで生成した鍵ファイルを読み込むのが一般的です。
# 例: openssl genrsa -out private_key.pem 2048
# 例: openssl rsa -in private_key.pem -pubout -out public_key.pem

# 秘密鍵(署名用)
PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAzsS26kYtQyHq586pWJq0H7rR6e2W8pXbXh0CgXz1y3B5Z4G2
... (実際はもっと長い秘密鍵の文字列) ...
-----END RSA PRIVATE KEY-----"""

# 公開鍵(検証用)
PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAztS26kYtQyHq586pWJq0
... (実際はもっと長い公開鍵の文字列) ...
-----END PUBLIC KEY-----"""

# 注: 上記の鍵は動作確認用のダミーです。
# 実際の鍵は安全に生成・管理してください。
# また、秘密鍵は絶対にコードに直接ハードコードせず、
# 環境変数やセキュアな設定管理ツールを使用してください。

# --- JWTの生成 ---
def create_rs256_jwt(user_id: str):
    payload = {
        "user_id": user_id,
        "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1), # 有効期限を1時間に設定
        "iat": datetime.datetime.utcnow(),                              # 発行日時
    }
    # ヘッダーとペイロードを秘密鍵で署名し、JWTを生成
    token = jwt.encode(payload, PRIVATE_KEY, algorithm="RS256")
    print(f"RS256 JWT 生成完了: {token}")
    return token

# --- JWTの検証 ---
def verify_rs256_jwt(token: str):
    try:
        # JWTを公開鍵でデコード(検証)
        decoded_payload = jwt.decode(token, PUBLIC_KEY, algorithms=["RS256"])
        print(f"RS256 JWT 検証成功!ペイロード: {decoded_payload}")
        return decoded_payload
    except jwt.ExpiredSignatureError:
        print("JWTの有効期限が切れています。")
        return None
    except jwt.InvalidTokenError:
        print("JWTが無効です(署名が不正または改ざんされています)。")
        return None

if __name__ == "__main__":
    # JWTを生成してみる
    my_jwt = create_rs256_jwt("bob456")

    # 生成したJWTを検証してみる
    if my_jwt:
        verify_rs256_jwt(my_jwt)

        print("\n--- 無効なJWTを試す(公開鍵を間違える)---")
        try:
            # 異なる公開鍵で検証しようとするとエラーになる
            wrong_public_key = PUBLIC_KEY.replace("MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAztS", "WRONGKEY")
            jwt.decode(my_jwt, wrong_public_key, algorithms=["RS256"])
        except jwt.InvalidTokenError as e:
            print(f"公開鍵が違うので検証失敗(想定通り): {e}")

        print("\n--- 無効なJWTを試す(改ざん)---")
        # JWTの一部を意図的に改ざんしてみる
        parts = my_jwt.split('.')
        # 署名部分を適当な文字列に書き換える
        tampered_jwt = f"{parts[0]}.{parts[1]}.invalid-signature-part"
        verify_rs256_jwt(tampered_jwt) # 署名が不正で検証失敗するはず

この例では、PRIVATE_KEY で署名を作り、PUBLIC_KEY で検証しているのが分かりますね。発行側は PRIVATE_KEY を、検証側は PUBLIC_KEY をそれぞれ安全に保持していれば良いので、鍵の共有という観点でのリスクが大幅に低減されます。

結局、どっちを選べばいいの?使い分けのポイント!

ここまで HS256 と RS256 の仕組みを見てきました。それぞれにメリット・デメリットがあることが分かったかと思います。では、実際にシステムを設計する際、どちらを選べば良いのでしょうか?

これは、あなたのシステムの「連携範囲」と「セキュリティ要件」によって決まります。

HS256 が向いているケース

  • 一つのアプリケーション内でJWTの発行と検証が完結する場合。
  • 密結合な少数のマイクロサービス間で、安全に秘密鍵を共有・管理できる場合。
  • パフォーマンスが非常に重要で、鍵の管理が限定的かつ強固にできる環境。
  • 例:モノリシックなWebアプリケーションのセッション管理。

RS256 が向いているケース

  • 複数の独立した、疎結合なサービス間でJWTを共有する場合。
  • 例えば、認証基盤(IdP)がJWTを発行し、そのJWTを多くの異なるサービスプロバイダ(SP)が検証するような、OpenID Connectなどの認証連携。
  • 高いセキュリティと信頼性が求められるシステム。
  • 秘密鍵の漏洩リスクを最小限に抑えたい場合。公開鍵は公開しても安全だからです。
  • 例:OAuth 2.0/OpenID Connect を利用したSSO(シングルサインオン)連携。

まとめ:セキュリティと利便性のバランス

JWTの署名アルゴリズム HS256 と RS256。どちらもJWTの「正当性」と「改ざん防止」を実現するための重要な仕組みです。

  • HS256 はシンプルで高速ですが、鍵の共有が課題。
  • RS256 は鍵の安全な配布が可能で高信頼性ですが、管理が少し複雑で計算コストも高め。

どちらを選ぶかは、あなたのシステムのアーキテクチャやセキュリティポリシー、そしてコスト(複雑性やパフォーマンス)とのバランスによって決まります。

インフラやネットワークの設計では、このように「何のために、どの技術を選ぶか」という判断が非常に重要になります。小難しいパケットの裏側には、必ずこうした「安全に、効率よく情報をやり取りしたい」という人々の願いが詰まっているんですね。

これからも、Web APIの世界を駆け巡るパケットたちが、安全で信頼性の高い旅を続けられるよう、一緒に学びを深めていきましょう!

それでは、また次回の記事でお会いしましょう!ネットワークの世界は奥が深くて面白いですよね!

コメント

タイトルとURLをコピーしました