JWT(JSON Web Token)の「仕組み」と「注意点」

JWT(JSON Web Token)は、2者間で安全にクレーム(ユーザー情報など)をやりとりするための方式です。ここでは、JWTの「仕組み」と「セキュリティ上の注意点」について確認します。

JWTの構成要素

3つの構成要素

JWTは以下のような文字列になります。

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.iIr5BW1YfvKF3hK9_1tyf-hGvDs7G7mz8j59pOvi2sp5aX6_Zl0upHXLajbLL574UeB6yQqOxDAh0-WUPnqTLJxbtfIDe3Ni1GWcg4pKf9G0QVOw2EK4_PiSJyf1FAIouXrCgDGJRwFXwIRxlPrTfboCCo68hgXFBAMKLcJW7Pc

ピリオド区切りで3つの役割を持っています。

ヘッダー.ペイロード.電子署名
Debuggerで確認
下記サイトにて、JWTの構造を手軽に確認できます。

https://jwt.io/#debugger

ヘッダー|アルゴリズムを伝える

ヘッダー部分は以下のようなJSONを Base64エンコード した文字列になります。 電子署名 がどういったアルゴリズムで作られているか伝えるのに利用されます。

{
  "alg": "RS256",
  "typ": "JWT"
}
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
Base64エンコードとは?
データを 数字(0~9) アルファベット(a~z, A~z) 記号(+, /)の64文字で表すエンコード方式です。

https://ja.wikipedia.org/wiki/Base64

alg では、以下のようなアルゴリズムを指定できます。

algの値 解説
none 署名なし
HS256 共通鍵方式。
署名の作成、検証に 共通の鍵 を利用。

HMAC using SHA-256 hash
RS256 公開鍵方式。
署名の作成に 秘密鍵 を利用。署名の検証に 公開鍵 を利用。

RSA using SHA-256 hash
SHA-256
SHA-2というハッシュ関数です。SHA-2の中でも、ハッシュ長が256ビットであることを表しています。SHA-2のハッシュ長は他にもバリエーションがあります( SHA-384 SHA-512など)。SHA-512のほうがビット長が大きく安全性が高いですが、一般的に利用されるのはSHA-256です。

ペイロード|ユーザー情報など

ペイロード部分は以下のようなJSONを Base64エンコード した文字列になります。

クレーム(認証対象となるエンティティに関する情報) を含みます( ユーザーID など)。

{
  "sub": "1234567890"
}
eyJzdWIiOiIxMjM0NTY3ODkwIn0

予約項目

クレームには任意の情報を含めることができますが、すでに役割が定義されている 予約項目 も存在します。

予約項目 概要
iss Issuer
発行者識別子。
sub Subject
同じIssuer内でユニークとなる値。
exp Expiration Time
JWTの有効期限。
ndf Not Before
JWTが有効となる開始日時。
iat Issued At
JWTの発行日時。
jti JWT ID
JWTの一意の識別子。重複が生じないように割り当てる。

参考

署名(signature)|改ざんの検証

トークンが改ざんされていないか検証するために利用されます。
PHPで署名を作る場合、実装イメージは以下のようになります。

HS256の場合

public function createSignature($header, $payload, $key)
{
    $unsignedToken = $this->encodeBase64url($header) . '.' . $this->encodeBase64url($payload);

    $signature = hash_hmac('sha256', $unsignedToken, $key, true);
    return $signature;
}

RS256の場合

public function createSignature($header, $payload, $key)
{
    $unsignedToken = $this->encodeBase64url($header) . '.' . $this->encodeBase64url($payload);

    $signature = '';
    openssl_sign($unsignedToken, $signature, $key, OPENSSL_ALGO_SHA256);
    return $signature;
}

セキュリティ注意点

algの改ざん

ヘッダーは Base64エンコード されているだけなので、 alg を改ざんすることができます。
algnone HS系 に改ざんして、検証を回避する脆弱性が存在します。

JWT偽造例(alg=none)

1. サーバーからJWTを以下形式で受け取る

algがRS256のヘッダー.ペイロード.電子署名

2. 攻撃者がJWTを改ざん

  • algを none に改ざん
  • ペイロードを改ざん
  • 署名を削除
algがnoneのヘッダー.改ざんされたペイロード.

3. 改ざんされたJWTをサービスが受けつける

algが none なので署名の検証が行われずに、改ざんされたペイロードが利用される。

対策

利用可能な alg をホワイトリスト形式で制限するなどの対応が必要です。

e.g.) RS256 のみ取り扱うように実装する。

参考