Carpe Diem

備忘録

JWSをローカルでデコードする方法

概要

JWS(JWT系アクセストークンやAppleのレシートなど)の中身をサクッと見たいけれど、Web上のサービスを使うとログに残ったりして情報漏洩のリスクがあるのでローカルでデコードしたい場合の手順です。

環境

ダミーデータ

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

というダミーのJWSがあるとします。

中身は以下です。

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

payload

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

signature

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

対応方法

の2通りのやり方で説明します。

ワンライナー

Base64エンコードされた文字列の長さが4の倍数でない場合、パディングとして等号(=)が必要になります。

echo "YOUR_JWS" \
| tr '.' '\n' | head -n 2 \
| awk '{ len=length($0) % 4; if (len == 2) $0=$0 "=="; else if (len == 3) $0=$0 "="; print $0; }' \
| base64 -D | jq .

ポイント

  • trでピリオドを改行に置き換え、それぞれのセグメントを新しい行に分割
  • headで署名部分の破棄
  • awk で文字列の長さをチェックし、必要に応じてパディングを追加

結果

期待通りデコードできました。

$ echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" | tr '.' '\n' | head -n 2 | awk '{ len=length($0) % 4; if (len == 2) $0=$0 "=="; else if (len == 3) $0=$0 "="; print $0; }' | base64 -D | jq .
{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

コード

こちらもパディング処理を挟んだ上でデコードします。

#!/usr/bin/env python3
import base64
import json
import sys


def base64url_decode(input):
    padding = 4 - (len(input) % 4)
    if padding != 4:
        input += "=" * padding
    return base64.urlsafe_b64decode(input)


def decode_jws(jws):
    header_b64, payload_b64, signature_b64 = jws.split(".")

    header_json = base64url_decode(header_b64).decode("utf-8")
    payload_json = base64url_decode(payload_b64).decode("utf-8")

    header = json.loads(header_json)
    payload = json.loads(payload_json)

    return header, payload, signature_b64


if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: decode_jws.py <JWS>")
        sys.exit(1)

    jws_token = sys.argv[1]
    header, payload, signature_b64 = decode_jws(jws_token)

    print("Header:", json.dumps(header, indent=2))
    print("Payload:", json.dumps(payload, indent=2))
    print("Signature:", signature_b64)

結果

こちらも期待通りデコードできました。

$ ./main.py "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
Header: {
  "alg": "HS256",
  "typ": "JWT"
}
Payload: {
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
Signature: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

まとめ

JWSの中身をローカルでサクッとデコードして確認する方法を紹介しました。