JWT

JWT #

JWT (JSON Web Token)はWebサービスにアクセスする際に送付するアクセストークンに使われるデータです。

JWTはヘッダ、ペイロード、シグネチャの3つの部分からなりまる。それぞれのフィールドはBASE64でエンコードされ、.で接続した文字列となります。

シグネチャがついているため、秘密鍵が分からない限りは、改変ができない仕組みになっています。

なお、ヘッダ、ペイロードともに暗号化はされていないので、base64デコードすれば中身は見ることができます。

Go #

ペイロードには"sub"(Subject)という項目に"user_name"を入れています。

JWTの生成にはgolang-jwtを使います。

package main

import (
 "encoding/base64"
 "encoding/hex"
 "fmt"
 "github.com/golang-jwt/jwt/v5"
 "strings"
)

func main() {
 // Create Token
 sub_name := "user_name"
 token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"sub": sub_name})

 keyString := "e8ad309ce40f7c7a45e237c30b7b64aabd0a743edb88d08465e10512c13f2a0f"
 key, err := hex.DecodeString(keyString)
 if err != nil {
  panic(err)
 }

 s, err := token.SignedString(key)
 if err != nil {
  panic(err)
 }

 fmt.Println(s)

 for _, t := range strings.Split(s, ".")[:2] {
  u, err := base64.RawURLEncoding.DecodeString(t)
  if err != nil {
   panic(err)
  }
  fmt.Println(string(u))
 }

 // Verify Token
 token, err = jwt.Parse(s, func(token *jwt.Token) (interface{}, error) {
  key, err := hex.DecodeString(keyString)
  if err != nil {
   panic(err)
  }
  return key, nil
 })

 if err != nil {
  panic(err)
 }

 claims := token.Claims.(jwt.MapClaims)
 username, err := claims.GetSubject()

 if err == nil && token.Valid {
  fmt.Printf("subject = %s\n", username)
 }
}

Python #

PyJWTをpipでインストールしておきます。

pip install PyJWT
import jwt
import base64

key_str = "e8ad309ce40f7c7a45e237c30b7b64aabd0a743edb88d08465e10512c13f2a0f"
key = bytes.fromhex(key_str)

payload = {"sub" : "user_name"}
token = jwt.encode(payload, key=key, algorithm="HS256")
print(token)

for t in token.split(".")[:2]:
    print(base64.urlsafe_b64decode(t + '==='))

payload = jwt.decode(token, key, algorithms=["HS256"])
username = payload.get("sub")

if username is not None:
    print(f"subject = {username}")

Deno #

import { create, verify } from "https://deno.land/x/djwt/mod.ts";

const keyString = "e8ad309ce40f7c7a45e237c30b7b64aabd0a743edb88d08465e10512c13f2a0f";

const keyData = new Uint8Array(keyString.length / 2);

for (let i = 0; i < keyString.length / 2; i++) {
    keyData[i] = parseInt(keyString.substring(i * 2, (i + 1) * 2), 16);
}

const key = await crypto.subtle.importKey(
    "raw",
    keyData,
    { name: "HMAC", hash: "SHA-256" },
    true,
    ["sign", "verify"],
);

const jwt = await create({ alg: "HS256", typ: "JWT" }, { sub: "user_name" }, key);
console.log(jwt)

const payload = await verify(jwt, key);
console.log(payload)

Rust #

cargo add jsonwebtoken
cargo add serde
use serde::{Serialize, Deserialize};
use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey};
use std::num::ParseIntError;
use std::collections::HashSet;

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
}

fn main() {
    let my_claims = Claims{sub:"user_name".to_string()};

    let key_str = "e8ad309ce40f7c7a45e237c30b7b64aabd0a743edb88d08465e10512c13f2a0f";
    let key = decode_hex(key_str).unwrap();

    let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(&key)).unwrap();
    println!("{}", token);

    let mut my_validation = Validation::new(Algorithm::HS256);
    my_validation.required_spec_claims = HashSet::new();
    let dec_claims = decode::<Claims>(&token, &DecodingKey::from_secret(&key), &my_validation).unwrap();
    println!("{:?}", dec_claims);
}

fn decode_hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
    (0..s.len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&s[i..i + 2], 16))
        .collect()
}