JSON Web Token(JWT)について調べていたら、JWTは絶対に使ってはいけないとかいろいろ書かれていて、使ってよいか良くわからなかった。
なので以下の点を調べてみました。
- JWTはなぜ使ってはいけない、と言われているのか
- JWTの代替案はあるのか
なお、私はただのフロントエンジニアでセキュリティの専門家ではないので素人の個人的見解です。注意してください。
TL;DR
- JWTは間違えやすく、脆弱になりがち
- JWTの代替はPASETOが良さそう
JSON Web Tokenとは
JSON Web Token(JWT)はセキュアなトークンを発行するための標準仕様です。
個人的に以下の点が特徴的だと思います。
- 標準仕様
- 仕様がシンプル
- 任意のデータをトークンに含められる
- トークンの偽造、否認は出来ない
- 暗号化されていないので中身は簡単に見られる
- 署名アルゴリズムが選択可能
JWTの仕様
ざっくり説明すると、JWTのトークンは以下のフォーマットの文字列です。
token = encode_base64url(header) + "." + encode_base64url(payload) + "." + encode_base64url(signature)
header = JWTの署名アルゴリズム等に関するメタデータが入ったJSON文字列
payload = 任意のデータが含められるJSON文字列
signature = 署名 = sign(signingKey, encode_base64url(header) + "." + encode_base64url(payload))
sing = 署名に使うアルゴリズム(headerにて指定されている)
何が問題なの?
署名アルゴリズムが選択可能になっていることで以下のセキュリティホール、リスクを生み出しています。
- 署名の検証を通過できる任意のpayloadを偽造可能
-
none
という「署名なし」のアルゴリズムが仕様で実装必須 - 公開鍵方式のアルゴリズムを選択すると、簡単に署名を偽造できる脆弱性がある。
-
- 「アルゴリズムの選択」という専門的な内容が開発者任せになっている
- 脆弱性のあるライブラリ実装が多い(多かった?)
これらの点は「署名アルゴリズムが特定の共通鍵であること」をチェックすれば回避できます。
しかしながら、仕様ではheaderに指定されたアルゴリズムを解釈・処理することを求めているので「特定のアルゴリズムしか受け付けないのは仕様非準拠である」という意見もあります。
JWTの代わりはあるの?
調べてみると以下のトークンが代わりに使えそうでした
fernet token
個人的に以下の点が特徴的です。
- 任意のデータも含められる(JSON文字列以外も使える)
- データは暗号化されているので鍵を持ってないと中身をみれない
- 暗号化、署名アルゴリズムがAES128固定
- バイナリを扱う必要がある
- 言語によっては少し面倒かも
フォーマット
token = encode_base64url(version + timestamp + iv + chiperText + hmac)
version = fernet tokenのバージョン(8bit)
timestamp = タイムスタンプ(64bit)
iv = 暗号化の初期化ベクタ(128bit)
chiperText = crypto_AES128(cryptoKey, 任意テキスト(データ)) = 暗号化された任意のテキスト(128bitの倍数)
hmac = sign_hmac(signerKey, version + timestamp + iv + chiperText) = 署名
branca token
fernetをベースに簡単にあつかえるようにしたトークンのようです。
個人的に以下の点が特徴的です。
- 任意のデータも含められる(JSON文字列以外も使える)
- データは暗号化されているので鍵を持ってないと中身をみれない
- 暗号化、署名アルゴリズムが
IETF XChaCha20-Poly1305
固定 - バイナリを扱う必要がある
- 言語によっては少し面倒かも
フォーマット
token = encode_base64url(version + timestamp + nonce + chiperText + tag)
version = branca tokenのバージョン(1byte)
timestamp = タイムスタンプ(4byte)
nonce = ランダムなデータ(24byte)
chiperText = crypto_poly1305(cryptoKey, 任意テキスト(データ)) = 暗号化された任意のテキスト(1byteの倍数)
hmac = sign_poly1305(signerKey, version + timestamp + nonce + chiperText) = 署名
PASETO
JWTのセキュアな代替として提案されているようです。
個人的にはポストJWTの大本命
- JSON文字列で任意のデータを含められる
- 署名アルゴリズムは固定
- 公開鍵、共通鍵のどちらの場合にも対応
- 共通鍵の場合はデータは暗号化される
- GitHubのスター数おおい(2018/7時点で1700ぐらい)
フォーマット
token = version + "." + purpose + "." + encode_base64url(payload)
version = "v1" | "v2"
purpose = "local" | "public" (local = 共通鍵, public = 公開鍵)
payload = 元データと鍵から作成したデータ
- payloadはversionとpurposeの組み合わせによって作成方法、検証方法が細かく規定されています。
まとめ
JWTの代替としてはPASETOが良さそう