Skip to content
nishi_74322014 edited this page Sep 1, 2026 · 1 revision

JWS

概要

以下の内容で最終確認済み。
https://tools.ietf.org/html/rfc7515

  • ざっくり言って「MAC や署名付きの JWT = JWS

  • JWT と言えば、だいたい、JWS の事を言う。

  • JWS は、暗号化ではなく署名なので、JSON の中身は誰でも見られる。

  • 通常、発行者だけが秘密鍵で検証できるが、

    • 秘密鍵(共通鍵)を使用すれば発行者だけが検証可能。
    • 公開鍵を使用すれば誰でも検証可能。
  • OpenID Connect の ID Token などで利用されている。
    OAuth 2.0 の Access Token としても使用されている。

移行メモ(正誤): 元ページの「秘密鍵を使用すれば発行者だけが検証可能」は、
文脈上 HMAC(共通鍵)を使う HS256 系の話である。
公開鍵暗号の「秘密鍵」で検証するのではなく、
発行者と検証者だけが知る共通鍵で MAC を検証する、という意味になる。

方式 生成に使う鍵 検証に使う鍵 検証できる者
HS256(MAC) 共通鍵 同じ共通鍵 鍵を持つ者だけ(=偽造もできる)
RS256 / ES256(署名) 秘密鍵 公開鍵 誰でも(偽造はできない)

複数の RP にトークンを配る IdP は、必ず後者を使う。

構成要素

JOSEヘッダ

JWS の基本的なヘッダ(≒ JWS Compact Serialization の場合の保護ヘッダ)には、
以下のものがある。

ヘッダの例

  • HS256
    共通鍵で検証可能な HS256(HMAC using SHA-256 hash)

    {
      "alg": "HS256",
      "typ": "JWT"
    }
  • RS256
    公開鍵で検証可能な RS256(RSA using SHA-256 hash)

    {
      "alg": "RS256",
      "typ": "JWT"
    }
  • ES256
    公開鍵で検証可能な ES256(ECDSA using P-256 and SHA-256)

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

追加のパラメタ

  • JWKをサポートする場合
    jwk, kid, jku などのパラメタを追加する。

  • 証明書をサポートする場合

パラメタ 内容 エンコーディング
x5u X.509 URL PEM エンコーディング
x5c X.509 証明書チェーン DER エンコーディングの base64(base64url ではない)
x5t X.509 証明書 SHA-1 拇印 DER エンコーディングの base64url
x5t#S256 X.509 証明書 SHA-256 拇印 DER エンコーディングの base64url

補足(最新化): x5t(SHA-1 拇印)は
SHA-1 の衝突困難性が破られている(2017 年 SHAttered)ため、
新規実装では x5t#S256 を使うこと。

また、jku / x5u は「鍵を取りに行く URL」であり、
攻撃者が制御する URL を書き込まれると
攻撃者の鍵で署名したトークンを受理してしまう。
取得先は許可リストで縛るか、そもそもサポートしないこと。

  • その他のパラメタ
    • typ
    • cty

TLS要件

jku および / または x5u ヘッダ・パラメタをサポートする実装は、TLS が必要。

ペイロード(クレームセット)

JWT を参照。

署名

  • 色々な署名・MAC アルゴリズムを使用して作成した署名

  • 署名の検証キーの共有方法には以下の方法が有る。

    • 検証用エンドポイントを使用する。
    • JWKを使用する。
    • X.509証明書を使用する。
  • 参考 : OpenID Connect の署名検証 - Carpe Diem
    http://christina04.hatenablog.com/entry/2015/01/27/131259

    • 検証方法①:Google の検証用エンドポイントを利用する
    • 検証方法②:IdP(Google)の提供している公開鍵を使って自分で検証する
      • 公開鍵①:JWK(JSON Web Key)を使う
      • 公開鍵②:X.509証明書を使う

移行メモ(正誤): 元ページは「公開鍵①」「公開鍵①」と
同じ番号が 2 つ並んでいたので、②に改めた。

詳細

表現(エンコード)

  • 構成要素(ヘッダ、ペイロード(クレームセット)、署名)を
    以下のように表現(エンコード)したもの。
  • 以下の 2 つの表現(エンコード)方法があり、
    どちらでも、構成要素は base64url でエンコードされる。

JWS Compact Serialization

多くの場合は、この表現(エンコード)。

  • 署名付きのデータを JSON(の Base64 URL Encode)形式で表現する。

  • 「Base64」ではなく「Base64 URL」なので「=」は含まれない。


  • ヘッダ.ペイロード(クレームセット).署名

    BASE64URL (UTF-8 (Header))
    .
    BASE64URL (UTF-8 (Claim Set))
    .
    BASE64URL (Signature)
    

※ 殆どの場合、こちらが使用されている。

移行メモ(正誤): 元ページの 3 行目は
BASE64URL (UTF-8 (Signature)) となっていたが、
署名はバイト列であって UTF-8 テキストではないため、
BASE64URL (Signature) が正しい。

JWS JSON Serialization

  • あまり市民権を獲得しているようには思えない表現(エンコード)。

    • pure な JSON として表現されるフォーマット
    • URL-Safe でもなければコンパクトでもない。
    • MAC や署名を複数持てるといった違いがある。
  • Syntax

    • protected = BASE64URL(UTF8(保護ヘッダ))
    • signatures
      • header = 非保護ヘッダ(JSON
      • payload = BASE64URL(ペイロード(クレームセット))
      • signature = BASE64URL(署名)
    {
     "payload":"<payload contents>",
     "signatures":[
      {"protected":"<integrity-protected header 1 contents>",
       "header":"<non-integrity-protected header 1 contents>",
       "signature":"<signature 1 contents>"},
      {"protected":"<integrity-protected header N contents>",
       "header":"<non-integrity-protected header N contents>",
       "signature":"<signature N contents>"}]
    }
  • Example

    • Complete JWS JSON Serialization Representation

      {
       "payload":
        "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF
         tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
       "signatures":[
        {"protected":"eyJhbGciOiJSUzI1NiJ9",
         "header":
          {"kid":"2010-12-29"},
         "signature":
          "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZ
           mh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjb
           KBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHl
           b1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES
           c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AX
           LIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"},
        {"protected":"eyJhbGciOiJFUzI1NiJ9",
         "header":
          {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
         "signature":
          "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS
           lSApmWQxfKTUJqPP3-Kg6NU1Q"}]
      }
    • JWS Using Flattened JWS JSON Serialization
      MAC や署名が 1 つのみの場合は Flattened JWS JSON Serialization を使える。

      {
       "payload":
        "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF
         tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
       "protected":"eyJhbGciOiJFUzI1NiJ9",
       "header":
        {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
       "signature":
        "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS
         lSApmWQxfKTUJqPP3-Kg6NU1Q"
      }

保護ヘッダ・非保護ヘッダ

JOSE ヘッダは、以下のメンバの和集合。

  • JWS Compact Serialization の場合は、JOSE ヘッダ ≒ 保護ヘッダ。
  • JWS JSON Serialization の場合は、
    JOSE ヘッダ ≒ 保護ヘッダ and / or 非保護ヘッダ

保護ヘッダ

MAC や署名によって完全性が保護されているヘッダ・パラメタを含む JSON オブジェクト。

  • JWS Compact Serialization の場合、これは JOSE ヘッダ全体で構成される。
  • JWS JSON Serialization の場合、
    • これは JOSE ヘッダの 1 コンポーネント。
    • 用例を解析してみると、JOSE ヘッダ中の alg パラメタが抽出されている。

非保護ヘッダ

  • これは、JWS JSON Serialization を使用する場合にのみ存在。

    • JOSE ヘッダの 1 コンポーネントであるが、
    • こちらは、署名の作成には利用されない。
  • 完全性保護されていないヘッダ・パラメタを含む JSON オブジェクト。

  • 用例を解析してみると、JOSE ヘッダ中の kid パラメタが抽出されている。
    (…kid が改ざんされると検証できなくなるだけなので、
    保護しなくてもイイためとは言えそう)

補足: 元ページの推測は概ね正しい。kid は「どの鍵か」を指すヒントであり、
改ざんされても検証に失敗するだけで、偽造の成立には繋がらない。

ただしこれは「受信側が kid を鍵の許可リストの索引としてのみ使う
場合に限る。kid をそのままファイルパスや SQL に渡す実装では
パス トラバーサル・SQL インジェクションの入口になるため注意。

アルゴリズム

認証系であれば公開鍵暗号化方式の RS256(RSA using SHA-256 hash)が良い。

  • HS256 — キー付きハッシュである alg=HS256(HMAC-SHA256)による MAC 付与

  • RS256 — 公開鍵暗号方式である alg=RS256(RSA-SHA256)による署名を行う。

  • ES256 — 公開鍵暗号方式である alg=ES256(ECDSA using P-256 and SHA-256)による署名を行う。

  • JWAを確認

補足(最新化): 現在の推奨は次の通り。

alg 位置づけ
RS256 相互運用性が最も高い。既定の選択肢
PS256 RSASSA-PSS。RSA を使うなら本来こちらが望ましい
ES256 鍵・署名が短い。IoT やモバイルで有利
EdDSA(Ed25519) RFC 8037。実装ミスに強いが対応実装はやや限定的
HS256 発行者と検証者が同一の閉じた系のみ
none 使用禁止(受理してもならない)

手順

作成

JWS Compact Serializationの場合

  • JWS を JOSE ヘッダ(= 保護ヘッダ)、ペイロード(クレームセット)の
    JSON を生成し、

    • JOSE ヘッダ(= 保護ヘッダ)を UTF-8 のバイト文字列に変換し、BASE64url でエンコード。
    • ペイロード(クレームセット)を UTF-8 のバイト文字列に変換し、BASE64url でエンコード。
  • BASE64url エンコード済みのヘッダとペイロード(クレームセット)をピリオドで連結、

  • 上記をヘッダで指定したアルゴリズムで MAC・署名を生成し、
    BASE64url エンコードし、当該要素をピリオドで連結する。

JWS JSON Serializationの場合

JWS Compact Serialization との差異は以下。

  • JOSE ヘッダ(= 保護ヘッダ and / or 非保護ヘッダ)
  • 非保護ヘッダは、MAC・署名の生成に使用されない。
  • 必要に応じて、MAC・署名の生成を繰り返す。

検証

JWS Compact Serializationの場合

  • JWS を JOSE ヘッダ(= 保護ヘッダ)、ペイロード(クレームセット)、
    MAC・署名に分割し

    • JOSE ヘッダ(= 保護ヘッダ)を BASE64url でデコードし、UTF-8 に変換。
    • ペイロード(クレームセット)を BASE64url でデコードし、UTF-8 に変換。
    • MAC・署名を BASE64url でデコード。
  • JOSE ヘッダの JSON から JWT の種類(JWS)、アルゴリズムを確認する。

  • 署名方法(アルゴリズム、キー)を考慮して JWS の検証を行なう。

  • 検証完了後、ペイロード(クレームセット)の JSON 内の有効期限の検証も行なう。

補足(順序が重要): 上の手順は
「ヘッダの alg を見てから検証方法を決める」ように読めるが、
それをやってはならない

正しくは「自分が受け入れる alg と鍵をあらかじめ決めておき、
ヘッダの alg がそれと一致するかを検査する
」順序になる。
ヘッダの値を信じて分岐すると、alg: none
RS256→HS256 すり替え攻撃が成立する(JWT を参照)。

また、署名検証より前にペイロードを業務ロジックへ渡さないこと。

JWS JSON Serializationの場合

JWS Compact Serialization との差異は以下。

  • JOSE ヘッダ(= 保護ヘッダ and / or 非保護ヘッダ)
  • 非保護ヘッダは、MAC・署名の検証に使用されない。
  • 必要に応じて、MAC・署名の検証を繰り返す。

具体例

アルゴリズム RFC 7515 の付録
HS256 https://tools.ietf.org/html/rfc7515#appendix-A.1
RS256 https://tools.ietf.org/html/rfc7515#appendix-A.2
ES256 https://tools.ietf.org/html/rfc7515#appendix-A.3

移行メモ(正誤): 元ページは「具体例」節の見出しと
アンカーの対応がずれており、HS256 の項に RFC の A.2(RS256)が、
RS256 の項に A.1(HS256)が置かれていた。上表で正した。

セキュリティに関する考慮事項

鍵エントロピー

すべての鍵に最低 128 ビットのエントロピーを使用する必要がある。
コンテキスト次第で、さらに多くのエントロピーが必要になる可能性がある。

ランダム値

  • パブリック / プライベートキーペア、MAC キー、パディング値を
    ランダムに生成する必要がある。
  • 暗号鍵を生成するために適切な擬似乱数生成器(PRNG)を使用する。
    (.NET では、PRNG として、
    RNGCryptoServiceProviderを使用する。)

補足(最新化): RNGCryptoServiceProvider
**.NET 6 以降で非推奨(SYSLIB0023)**である。
現在は System.Security.Cryptography.RandomNumberGenerator
静的メソッドを使う。

byte[] key = RandomNumberGenerator.GetBytes(32);   // 256 bit

鍵の識別

仕様の外だが、以下を使用できる。

鍵の保護、発信元認証

MAC

  • 保護

    • MAC キーを保護する必要がある。
    • MAC キーが破損すると、認証されたコンテンツの変更が検出されなくなる可能性がある。
  • 発信元認証
    鍵の出所の認証が必要。

署名

  • 保護

    • 秘密鍵を保護する必要がある。
    • 署名者の秘密鍵が侵害されると、攻撃者は署名者になりすますことができる。
  • 発信元認証
    データ発信元の認証が必要。

署名とMACの違い

  • 共通点

    • 完全性チェックを提供
      完全性値が計算されてからメッセージが変更されていないことを確認
  • 相違点
    セキュリティプロパティにはいくつかの重要な違いがある。

  • 特定の状況下でのみ発信識別を提供するため、
    第三者への発信を証明するために使用できない。

    • MAC キーが 2 つのエンティティにしか知られておらず、
      受信者がメッセージを作成していないことが分かっている場合にのみ発信を判断できる。
    • これは、メッセージが対称 MAC キーを知っている当事者のうちの
      1 つによって生成されたという裏付けを提供するだけ。
  • 秘密鍵と MAC キー

    • 秘密鍵
      単一のエンティティの手元にある
    • MAC キー
      整合性の計算と検査に MAC キーを使用するすべてのエンティティの手元にある必要がある。

※ HS256 はプレーンな HMAC で MAC ではない(ややこしい)。

移行メモ(正誤): この末尾の注記は誤りである。
HMAC は MAC の一種であり、HS256 は
「HMAC-SHA256 という方式で作られた MAC」に他ならない。

意味
MAC メッセージ認証符号という概念(共通鍵で完全性を保証する)
HMAC ハッシュ関数から MAC を構成する方式(RFC 2104)
HS256 HMAC-SHA256 を JOSE の alg 名で呼んだもの

元ページの意図はおそらく
「CMAC など、ブロック暗号ベースの MAC とは別物」という区別だと思われる。

署名は秘密鍵の保有者しか生成できないため、否認防止(第三者への証明)が成立する。

暗号の敏捷性

JWAを参照

自作ライブラリ

署名・暗号化アルゴリズム

.NETの署名・暗号化アルゴリズムの署名・暗号化アルゴリズムを
使用すると良い。

KeyedHashAlgorithm

AsymmetricAlgorithm

補足(最新化): .NET Core 以降は、RSACryptoServiceProvider
ECDsaCng といった実装クラスを直接 new せず、
RSA.Create() / ECDsa.Create() を使うのが正しい。
Windows / Linux / macOS で適切な実装が選択される。

using var rsa = RSA.Create(2048);
byte[] sig = rsa.SignData(data, HashAlgorithmName.SHA256,
                          RSASignaturePadding.Pkcs1);   // RS256
// PS256 なら RSASignaturePadding.Pss

なお、実務で JWS を自作する必要はほとんどない。
.NET なら Microsoft.IdentityModel.JsonWebTokensJsonWebTokenHandler)を使う。

参考サイト

以下が参考になる。

ココを見ると、

  • { alg = "RS256", typ = "JWT" } と言うヘッダで、
  • RSACryptoServiceProvider + X509Certificate2 の署名を行っており、

これで、実際に Google 側での検証ができている。

検証サイト

上記のように、RFC を正確に理解していないと JWT ライブラリ作成は難しいが、
JSON Web Tokens - jwt.io などの検証サイトを使用して検証できれば、
及第点に達していると言える。

以下のように検証できる。

補足(取り扱い注意): 検証サイトに
本物のトークンや秘密鍵を貼り付けてはならない
動作確認はテスト用の鍵とダミーのクレームで行うこと。

手順

  1. 左ペイン(Encoded)に JWT の文字列を貼り付ける。
  2. すると、入力した JWT から、ペイン(Decoded)の Header、Payload に
    自動的に表示がなされる。
  3. 次に、上部 ALGORITHM selectbox から、Header に表示された alg
    同じアルゴリズムを選択する。
  4. 最後に、VERIFY SIGNATURE に、署名の検証に使用するキーを入力する
    (HS256 の場合と RS256 の場合で異なる)。

HS256の場合

HS256 は非常に単純で、署名に使用したキーの Base64(Base64Url)表現を指定する。

HS256

RS256の場合

RS256 は署名に使用した秘密鍵に対応する公開鍵を指定する。
私は、X.509証明書を利用したので、
OpenSSLで公開鍵を取得して、それを貼り付けた所、検証ができた。

ポイントは、

  • -----BEGIN PUBLIC KEY-----
  •      「公開鍵」
  • -----END PUBLIC KEY-----

と、ヘッダ・フッタ部分も貼り付ける必要がある所だろうか。

RS256

Open棟梁

参考


Tags: 移行, IT国際標準, プログラミング, 通信技術, 認証基盤, クレームベース認証, 暗号化

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally