最近、JWT JWT 言っているような気がしますが、ぼくはこの単語を OAuth 2.0 の文脈ではじめて知りまして、それから JWT まわりの RFC に目を通し、Auth0 が無料で出している JWT Handbook を読みました。 このエントリは、JWT に関連する仕様について、ぼくなりにまとめたものです。長くなるので、何エントリかに分けて投稿します。JWK と JWA はあまり興味がないので、JWT、JWS、JWE あたりが対象となる予定です。
JWT/JWS/JWE/JWA
JWT まわりの関連仕様は結構密接に関連しあっています。ぼくが理解しているこのあたりの仕様の概観は以下のとおりです。
略称 | 名称 | RFC | 概要 |
---|---|---|---|
JWT | JSON Web Token | RFC 7519 | 特定の「主体」に対する情報(JWT の用語で言うと "Claim")の urf-safe な JSON 表現 |
JWS | JSON Web Signature | RFC 7515 | 電子署名付きメッセージの表現 |
JWE | JSON Web Encryption | RFC 7516 | 暗号化メッセージの表現 |
JWK | JSON Web Key | (RFC 7517 | 暗号鍵の JSON 表現 |
JWA | JSON Web Algorithm | RFC 7518 | JWS や JWE などで使われる暗号アルゴリズム |
分かりやすくいえば JWT は一種の情報のセットでしかなく、そのエンコード形式として JWS や JWE がある、ということになります (この JWS や JWE は組み合わせて使えるのでさらに分かりにくいですが…) JWT の RFC 7519 に記載があるため、よく以下のような例が JWT として出てくるのですが、ぼくの理解が正しいようなら、これは厳密には JWT (の Claim Set) を JWS の "Compact Serialization" で表現したものになります。 ただ、このエンコード結果を「JWT」として呼ぶことも多いようなので、そのあたりはコンテキストを元にして判断するしかないかなと思います。なんか Unicode を UTF-8 と呼んでいるようで、ちょっと違和感があったりもするのですが…。
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9 . eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ . dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
というわけで、今日は JWS です。 JWS の意義は、改竄されていないことを保証するメッセージ(電子署名付きメッセージ)を Web で送信するフォーマットが標準化された、ということに尽きるように考えています。今後、そういうセキュリティが要求されるメッセージを送信する機会では、積極的に使っていきたいなと思います。
JWS 概観
JWS は、電子署名付きメッセージを JSON で表現するときのフォーマットを定義しています。 JWS のメッセージは以下の 3 つで構成されます。
- JOSE ヘッダ
- JWS ペイロード
- JWS 署名
また、この 3 つをどのようなフォーマットで表現(エンコード)するかについて、2 パターンが仕様化されています。
- JWS Compact Serialization
- JWS JSON Serialization
上にも載せていますが、"." で区切られたメッセージは JWS Compact Serialization の方です。 あまり市民権を獲得しているようには思えない JWS JSON Serialization の方は、pure な JSON として表現されるフォーマットで、Compact Serialization に対し、電子署名を複数持てるといった違いがあります。
JOSE ヘッダ
JOSE ヘッダは、電子署名、MAC に関する情報を保持する JSON オブジェクトです。このヘッダは、後日に書く(であろう)JWE と共通です。
ヘッダの内容は、Registered Header、Public Header、Private Header の 3 種に分けられており、署名に使うアルゴリズムを示す alg
や公開鍵の在処を示す jku
(JWK Set URL) などは Registered Header に該当し、RFC 上で仕様化されています。
Public Header は「定義していいけど名前の衝突を避けるために IANA に登録しろよ」というもの、Private Header は「衝突するかもしれないから注意しろよ」ってヤツで、アプリケーションの要件によって定義していくタイプのものです。
シンプルかつ JWT の文脈で良く出てくるのは以下のようなヘッダでしょうか。
{"typ":"JWT", "alg":"HS256"}
JWS ペイロード
大切にしたいメッセージ本体。なんでも良いです。JWS の目的は、このペイロードの改竄を防止することですね。
JWS 署名
JWS 署名は、JOSE ヘッダ、JWS ペイロードから計算されます。
JOSE ヘッダの alg
にて署名を計算するアルゴリズムが規定されますが、アルゴリズムのインプットとなるのは、以下の式になります。まぁ何となく分かりますね。
ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload))
フォーマット
JWS の表現方法として、2 種のシリアライゼーションフォーマットが定義されています。 JWT (JWS) はトークンとして使用されることが多いのですが、このように HTTP のヘッダや URI 上で表現される場合は、JWS Compact Serialization を使用します。実用例では、こちらしか見たことがありません…。 もう 1 つは、JSON で表現するという JWS JSON Serialization です。
JWS Compact Serialization
JWS Compact Serialization では、
- JOSE ヘッダ
- JWS ペイロード
- JWS 署名
のそれぞれをエンコードしたものを "." で区切って表現します。 RFC からそのまま引っ張ってくると、以下の表現になります。
BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload) || '.' || BASE64URL(JWS Signature)
JWS JSON Serialization
こちらは、URL-Safe でもなければコンパクトでもないので、誰が使うのかってかんじが個人的にしてしまいますが、JWS を JSON として表現します。 ぼくの感じたメリットは 2 点。
- 署名対象のヘッダと、そうでないヘッダを分けることができる
- 1 つの JWS のメッセージの中に、複数の署名を入れることができる。
{ "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>"}] }
じつはこのフォーマットにはもう 1 つ種類があり、署名が 1 つのみの場合は Flattened JWS JSON Serialization を使えます。
{ "payload":"<payload contents>", "protected":"<integrity-protected header contents>", "header":<non-integrity-protected header contents>, "signature":"<signature contents>" }