JWTに関して学んだ

2021/06/24に公開

概要

JWT の概要とその中で使われている技術に関して理解したようです。
よく認証・認可の仕組みで使われるものです。今回は中身がどの様になっているか調べました。
JWXについて詳しく述べているわけではありません。

JWTとは

詳しくはRFC 7519を参照。

JSON Web Token (JWT) is a compact, URL-safe means of representing
claims to be transferred between two parties. The claims in a JWT
are encoded as a JSON object that is used as the payload of a JSON
Web Signature (JWS) structure or as the plaintext of a JSON Web
Encryption (JWE) structure, enabling the claims to be digitally
signed or integrity protected with a Message Authentication Code
(MAC) and/or encrypted.

JSON エンコードされたデータを電子署名をつかって2者間で整合性が取れた状態でやり取りする仕組みです。

作り方を簡単に表現すると、アルゴリズムと署名したい claims と secret を用意して、ガッとすればできあがるものです。

JWT の構造

外形は、 Header.Payload.Signature です。
それぞれが . でつながっています。
それぞれの要素について見ていきます。

https://jwt.io/

このサイトで簡単に JWT がどうなるか確認できるのでお試しあれ。

まずは Header を用意します。
アルゴリズムをどうするかですが、利用できるものは以下です。

  • HS256
  • HS384
  • HS512
  • RS256
  • RS384
  • RS512
  • ES256
  • ES384
  • ES512
  • PS256
  • PS384

PS256 は見たことないですが、RSASSA-PSS using SHA-256 and MGF1 with SHA-256 ということっぽいです。
今回は発行者と検証者が一緒であることを想定しているので、HMacSHA256 で行います。

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

これを Base64 URL Encoding すると...
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
こうなります。

Payload

この payload のことは claims という表現をしています。
Claims-based identity の claims です。

{
  "loggedInAs": "admin",
  "iat": 1422779638
}

これも Base64 URL Encoding すると...
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

ちなみに iat は issued_at を表していて、発行日時を表していて、有効期限の始まりを意味します。
さて、あとはこれらのデータを使って署名するだけです。

Signature

これが JWT の肝ですね。
今回は HS256 でやるので secret は HMAC_SHA256 でシュッとつくります。

secret の key は完全なランダムな文字でキー長は 32byte 以上にしするよう気をつけます。

A key of the same size as the hash output (for instance, 256 bits for
"HS256") or larger MUST be used with this algorithm. (This
requirement is based on Section 5.3.4 (Security Effect of the HMAC
Key) of NIST SP 800-117 [NIST.800-107], which states that the
effective security strength is the minimum of the security strength
of the key and two times the size of the internal hash value.)

https://tools.ietf.org/html/rfc7518#section-3.2

https://crypto.stackexchange.com/questions/34864/key-size-for-hmac-sha256

適当にキーを作ります。

openssl rand -base64 40 | fold -w 40

aMsT1GV5kH5rWzaBfybJsTYpc0NSsyec5u6CBYjL

できました。

HMAC-SHA256(
  base64urlEncoding(header) + '.' +
  base64urlEncoding(payload),
  secret
)

それでは上の式によって作ります。

HMAC-SHA256(
  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ,
  aMsT1GV5kH5rWzaBfybJsTYpc0NSsyec5u6CBYjL
)

これを実行すると...。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.JQm7WDaqsDZnnvOJerRwpf9d8FFyevGK5-HZ7jAL5p0

無事出来上がりました:tada:。

おまけ

検証

さて,JWT を作るだけでなく、検証も必要になってきます。
まず、JWT を検証するにはどのアルゴリズムを使って、どの secret key を使ったかという情報が必要です。

secret key はどっかに保管されているところからよいしょともってくるだけで良いです。
アルゴリズムはどうでしょうか。そういえば、header に入れていたので取り出して使いますか...。
と header をデコードしておもむろに取り出そうとしてはいけません。なぜなら、header はいくらでも書き換え可能なので全くセキュアではないからです。

上では述べていませんが、alg: none というアルゴリズムを指定しない方法というのがありまして(https://tools.ietf.org/html/rfc7519#section-6)、これを利用した攻撃があったようです。

https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/

ざっくりというと、Header のアルゴリズムを none にして Payload を好き勝手いじって、 signature を空にするとなんでもおくれちゃうよってやつです。

ライブラリによっては、header のアルゴリズムをまるっと信じた設計になっているものがあり問題となったようです。

つまり、検証するアプリケーション側でアルゴリズムのチェックを行っておく必要があります。

RFC8725 では none はセキュアにやり取りできない場合は、使うべきでないということらしいです。
(その場合、そもそも JWT を使う意味が思いつきませんが...)

https://tools.ietf.org/html/rfc8725#section-2.1

とりあえずアプリケーション側でアルゴリズム自体の検証を挟むことが大事です。

claims

主体者の何者かを key:value で表したものくらいのイメージ
claim って直訳すると主張するっていう意味らしいですが、ちょっと何言ってるわかりません。
claims は主体(subject)が何をできるかのステートメントであり、アプリケーション側で必要なデータを記述するもののようです。

参照

GitHubで編集を提案

Discussion