JSON Web

Web API リクエストで良く使われる JWS と JWT を理解してみる

2018年7月16日

JWS / JWTとは?

JWS(JSON Web Signature) と JWT(JSON Web Token) を、良く聞くもののちゃんと知らなかったので調べてみました。
JWT の読み方は「ジョット」です。

JWSとJWTの関係性は以下のようになります。

このデータの固まりは「token」として、Webリクエストにくっつけて使われます。

HTTPヘッダーの中に埋め込まれたり、URL の引数につけて使う事ができます。

「token」は、いろんな文脈で使われるためいろんな意味がありますが、JWT / JWS の文脈で使われる場合は、Webリクエストの要求を受け取った側がそのリクエストが正規のものかどうか確認するために使われます

JWS(Json Web Singature) は、RFC 7515 として公開されている規格です。Signature なので「署名」を意味します

正確に言い表そうとすると難しいですが、「署名されたJSONオブジェクト」を指している事が通常だと思います。
(RFC的に細かく言うと署名される対象は JSON でなくても良いですが、一般的な文脈では JSON 以外を署名している事は私の観測範囲では見た事がないので、多分レアかと・・・)

JWS は、APIの世界で、APIサーバーがクライアントから受けた APIリクエストが正当なユーザー(許可を与えられたユーザー)からのリクエストかどうかを確認するために用いられています。

JWS による署名の基本的な仕組みは一般的な署名の仕組みと同じす。

JWS の仕組みをざっくりと言うと「署名アルゴリズム(どういうアルゴリズムで署名しているか) 」と「データペイロード (署名対象のデータ)」を BASE64 URL にエンコードした後、HASH 値を取り、秘密鍵と混ぜ合わせて HMACを取得してそれを署名とするものです。(RSA公開鍵暗号を使った署名もありますが、広く使われているのは HMAC だと思います)

署名対象のデータにそのまま署名するのでは無く、ハッシュ値を取って署名対象のデータをコンパクトにまとめてから「秘密鍵」を使って暗号化する(=署名する) 事で、後の署名検証負荷を考慮するという一般的な署名の考え方の部分は同じです。

加えてURLの一部として使っても問題なく取り扱えるように BASE64URL (Base64 Encodingからさらに URLで使用できない文字を別の文字に変更したもの)で Encoding する事を規格で決めています。

JWSは「JOSE Header」「JWS Payload」「JWS Signature」の3つから構成されています。

JWSでは、「JOSE Header」の部分で署名の対象のデータ(JWS Payload)が何か、署名の方式は何を使うかを定義しています。この部分の記述方式は JSON形式で表されています。
上の図では、署名の対象になる「JWS Payload」の部分は JWT (JSON 形式)になっていますが、Base64URL 形式であれば、必ずしも JSON である必用は無いとされています(ただ、実際のユースケースは JSON 以外は無いと思うので豆知識です)

The JWS Payload used in this example is the octets of the UTF-8 representation of the JSON object below.
(Note that the payload can be any base64url-encoded octet sequence and need not be a base64url-encoded JSON object.)

とは言え、Header がJSON であれば Payload の部分も JSON で行うのが自然な考えで、そのための規格が JWT(Json Web Token)です。JWTはRFC7519で定義されています。

上記の例では、「User ID」と「Expiration Date」がデータペイロードに含まれた絵を描いてます。
JWTでは、Claim (Keyと値=パラメーターの事)として、基本的な Key の値が予約されています。(独自の値を追加する事もできますが、衝突しないように注意しないといけません)

例えば、以下の例の "iss"は、"Issuer Claim(発行者)"、"exp"は、"Expiration Time Claim(有効期限)" として予約されている値です。

{"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true}

他にも exp が切れた JWT を処理してはいけない。等の決まりが定義されています。

JWT と JWS という言葉の定義

世の中的には、署名の付いた JSON オブジェクトの事を JWT と呼んでいるのが一般的だと思います。
ですが、その気持ちで JWT の RFC7519 を読んでいくと「?」となります。

JWT の RFC には以下のようにあります。

3.  JSON Web Token (JWT) Overview

JWTs represent a set of claims as a JSON object that is encoded in a JWS and/or JWE structure

.

ここに書いてある通り「JWTは、JWS または JWE(もしくは両方)の構造の中にエンコードして埋め込まれる、JSON 形式の Claim のセットです」とあります。(Claim とは JWS / JWT の中で使われる変数を指します。)
つまり JWT は単体として使われるものでは無く、JWS や JWE の中の部分の定義になります。

JWT と言うと"署名の付いた JSON オブジェクト"と言う文脈で使われている事が多いと思いますが、この定義を読むと  JWT は、署名対象のデータ部分の事で、署名は JWS のパーツの一部なので、署名の入った全体を表すには JWS と言うのが正しいのでは無いかと思っています。

この話は JWSの構造の解説の後で再度考えて見ます。

JWS の構造

以下は、ほぼ RFC7515 内に書かれている事になりますが、JWSの構造を少し詳しく解説していきます。
JWSは「JOSEヘッダー」「JWSペイロード」「JWS署名」の3つの部分で構成されています。

  • JOSE ヘッダー 

JOSE(JSON Object Signing and Encryption)ヘッダーです。
前述の絵で「署名アルゴリズム」と書いてある所です。以下のように、署名の対象になるデータの形式  ( typ : JWT )と、署名アルゴリズム (alg : HS256) が記載されています。

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

この typ や alg 等の Claim (パラメーター)は、適用されているデジタル署名やMACを表すClaimとしてJWSの RFC で予約されているものです。
ここでは、Payload の種類が JWTである事、署名アルゴリズムは HS256(HMAC SHA-256)である事をJSON形式のデータで示しています。
実際には上記のJSON型式のままデータを取り扱うのでは無く、以下のように上記を BASE64URL でエンコードした値にします。

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
  • JWS ペイロード

JWSペイロードとは、署名対象となるデータ部分です。実際には JOSEヘッダーも改竄を防ぐため署名の対象に含まれますが、署名したいご本尊はこの部分になります。
ここは JSON である必用はないのですが、一般的に JSON が使われていて、JSON の場合の定義を決めているのが JWT です。

JWT では、以下のようにJSON型式で記述します。

{"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true}

iss (発行者) や exp(有効期限) は、JWSでは無く、JWT で定義されている予約された Claim (パラメーター)です。
JWSではデジタル署名等に関わる Claim が定義されていましたが、JWT では実際の Web上でのやり取りで使用しそうな Claim を定義しています。
このJSONデータもこのまま取り扱うのでは無く、BASE64URLでエンコードした値に変換します。

eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
  • JWS署名

「JWS署名」は「JOSE ヘッダー」と、「JWSペイロード」を「・」でつないだ値を入力にして、 HMAC SHA-256 で署名を作り、BASE64URL に変換したものです。
少し数式ぽく表現すると

ASCII(BASE64URL(UTF8(JWS Protected ヘッダー)) || '.' || BASE64URL(JWS ペイロード))

を作成した後、このHMAC SHA-256のハッシュ値を作り、そえれを BASE64URLに変換したものが「JWS署名」です。

この例では以下のようになります。

dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
  • 上記の3つを足し合わす (JWS Compact Serialization)

上記の3つを「・」でつなげる手法を JWS Compact Serialization と呼び、つなげたものがJWSになります。以下がJWSです。
(RFCにはこれ以外の"シリアライゼーション”手法 (3つのデータを結合させる方法) が定義されていますが、JWS Compact Serialization が一般的だと思います)

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

JWSのヘッダーとペイロードと署名の3つを足したものの呼び方

はじめの方でも書きましたが、JWS の Header + Payload + Signature を足したものを何と呼ぶべきか、RFCを読んでいると少し悩んでしまいます
単純にJWSと呼ぶのが、恐らく正確だと思いますが、一方で慣習的に JWT と呼んでいるケースが多いと思います

JWT の RFC7519 にも JWSの3つのパーツをつなげる解説があり、以下のような記述があり"(ピリオドで部品をつなぐ事で)この完全なJWT(を生み出す)"という呼び方をしています。

Concatenating these encoded parts in this order with period ('.')
characters between the parts yields this complete JWT

JWSのRFC7515では同じものを以下のように”(ピリオドで部品をつなぐ事で)この完全なJWS表現(を生み出す)"としています。

Concatenating these values in the order Header.Payload.Signature with
period ('.') characters between the parts yields this complete JWS
representation
using the JWS Compact Serialization

これを読む限り RFC 自体が特に呼び方を気にしてないように見えますRFCは時々いい加減です

どちらかに呼び方を統一した方が混乱しないので、この記事では、3つの値を足し合わせた全体の物体を JWS と呼ぶ事にします。
また、文脈的にデータのペイロードに含まれる Claim がキモになっている場合は、JWTと呼ぶ事にします。

こちらのサイトは、JWT と言うタイトルのサイトになっています。
ですが、このサイトで取り扱うのは JWT というより、署名を作る事を目的としているので、JWSそのものです。

JWSのユースケース

JWSを APIの認証のToken に使うケースを下に書いてみました。

この例では、ユーザーは Token 発行サーバーに userid / password を渡して、Token の発行を依頼しています。
Token 発行サーバーに何を渡すように設計するかは、人それぞれだと思うのですが、ここでは userid と password で認証をする事で、そのユーザー本人である事を確認させています。

発行される Token には、ユーザーIDと、Token の有効期限を入れて見ました。
ユーザーID は、後でAPIサーバー側でアクセス権の範囲を見るために使い、有効期限はいつまでも Token が使い回せないようにするため入れました。

JWS の Token を手にしたユーザーは、APIサーバーにアクセスをします。

JWSのユースケース

図の下の APIサーバーでは、Token の検証を行います。Tokenは、HTTPのヘッダーに埋め込むのが一般的だと思います。

もちろん、HTTPヘッダーではなく、URLの引数にしてもOKです。Web上でいろんな使われ方ができるように BASE64でエンコーディングされています

API サーバーが Token の検証を行い、Token の改竄がなければ、少なくても userid / password を使って正規のユーザーが発行を受けた Token である事が保証できます。

もし、誰かが 正規のToken を盗んだ場合は、有効期限内であれば使えてしまいます。盗まれたユーザーの user id 値が入った Tokenを許可しないようにするか、逆に有効期限が切れる事で、Token を盗んだユーザーは使えなくなる事を対処法としてします。逆に有効期限の情報しか持ってない場合は、有効期限内はそのトークンはトークンを盗んだ人間が使い回せる事になります。

この例では、Token の中にはユーザー名を含んでいるので、そのユーザーが触れる範囲しかアクセスさせないようにしたり、Read までしか許可しないなど、APIサーバー側で「認可」を設定する事ができます。

 

署名の検証方法

署名の検証方法も一般的な署名と同じですが、この手の事はたまにしか考えないので、頭の体操に以下に図示してみました。
(Base64URLでエンコーディングする部分は省略しています)

 

RSA公開鍵による署名

RSA公開鍵方式による署名は、Webサイトの改竄 / 本人(サイト?)確認用に Webサイトの証明書で使われている考え方なので、考え方としては一般的に広く知られている方法だと思います。が、RSAは、JWSの署名の世界では、ほとんど使われてないと思います

Token発行側と API サーバーの間で公開鍵を共有できさえば良いので、公開鍵を広く公開する必用はありませんが、公開鍵を広く公開すれば Token を発行した人と、Token を検証する人が関係の無い人でも、公開鍵を使って Token の正当性を確認する事ができます。(前述したように、実際そういう使い方がされているケースがあるかは、よくわからず、あくまで理論的にありうるという事で例として上げておきます)

HMACによる署名

こちらは両方が共通の秘密鍵を持つ方法です。JWSを使った署名検証は、通常このタイプです

共通の秘密鍵が必用なので、Token を発行すると側とTokenを検証する側で共通の鍵を持っている必用があるので、システム間でなんらかの連携が取れている事が前提です。

一般的には APIサーバーが受け取る Token は、同じシステム内にある Token 発行サーバーが発行するので、共通秘密鍵の交換に複雑な交換方法を考える必用は無いと思います。

 

-JSON, Web
-, , ,

Copyright© エンジニアの何でもメモ帳 , 2020 All Rights Reserved.