LoginSignup
2
5

More than 5 years have passed since last update.

JWTのシグネチャ部分を自力で作る

Last updated at Posted at 2018-10-17

なぜやるの?

JWTを最近触っていてわかっていることは,改ざんチェックができるJSONくらいの認識しかない
JWTを触る上で少しでも理解していないといざというときに危ないと考えるため

どうやるの?

golangライブラリのjwt-goで作成したJWTをpythonで作り直す
ここでの縛りとしてpythonのライブラリは

  • JWTとは?
  • JWTのシグネチャ部分の構造
  • jwt-goで作成してみる
  • pythonでパースしてみる
  • pythonでjwtを作成する

JWTとは?

以前書いた記事を見ていただきたい

JWTのシグネチャ部分の構造

JWTのシグネチャ部分は以下のように生成される

ハッシュ関数(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

ハッシュ関数の第一引数にbase64UrlEncode(header) + "." +base64UrlEncode(payload)を,第二引数に秘密の鍵を渡してあげると生成することができる

jwt-goで作成してみる

main.go

package main

import (
    "fmt"
    "github.com/dgrijalva/jwt-go"
)

type jwtCustomClaims struct {
    Name  string `json:"name"`
    Admin bool   `json:"admin"`
    jwt.StandardClaims
}

func main() {
    claims := &jwtCustomClaims{
        "test",
        true,
        jwt.StandardClaims{
            ExpiresAt: 1539762311, //有効期限をを発行しておく
        },
    }
    j := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Secretで文字列にする. このSecretはサーバだけが知っている

    token, _ := j.SignedString([]byte("secret")) //学習用なのでerr処理は飛ばす

    fmt.Println(token)
}

ここではNameをtest,adminをtrueにして鍵をsecretにしている
ExpiresAt: 1539762311に関して,本来は現在の時刻を取得してあげればいいはず
今回は固定しておく

そうするとgo runの結果は以下のようになると思う

$ go run main.go
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o

何度か実行してみて常に同じ値が出されることを確認する

pythonでパースしてみる

jwt.ioでパースしてもいいが今回は自力でパースする

main.py

import sys,re
import base64
import json
import hashlib,hmac
import urllib.request

args = sys.argv
str = args[1].split('.')
print(args[1])
header = base64.b64decode(str[0])
payload = base64.b64decode(str[1])
print(header)
print(payload)

実行の仕方は

$ python main.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o 

長い.....ので

$ python main.py $(go run main.go)

上も下も実行結果は同じになるはずだ

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o
b'{"alg":"HS256","typ":"JWT"}'
b'{"name":"test","admin":true,"exp":1539762311}'

1行目はmain.pyを実行するときに引数に与えた文字列がそのまま表示されている
2行目はヘッダ部分をbase64でデコードした値
3行目も同様にペイロード部分をbase64でデコードした値になる

ここでわかることは....

webアプリケーションでjwtを使うとき少なくとも
ヘッダ,ペイロードは見ることができるということだ

pythonでjwtを作成する

ここまででわかっていることは

  • 秘密の鍵 -> secret
  • ヘッダ -> {"alg":"HS256","typ":"JWT"}
  • ペイロード -> {"name":"test","admin":true,"exp":1539762311}
  • jwt -> 下に書いた
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o

ここからシグネチャ部分を生成していく

全体のコードはこうなる
main.py

import sys,re
import base64
import json
import hashlib,hmac
import urllib.request

args = sys.argv
str = args[1].split('.')

print(args[1])
header = base64.b64decode(str[0])
payload = base64.b64decode(str[1])
print(header)
print(payload)

hp = base64.urlsafe_b64encode(header)+b'.'+base64.urlsafe_b64encode(payload)
print(hp)

secret = b'secret'
signature = hmac.new(secret,hp, hashlib.sha256)
signature = signature.digest()
signature = base64.urlsafe_b64encode(signature)


jwt = (hp+b"."+signature).decode("UTF-8").strip("=")
print(jwt)

以下は詳細を表記しているが,その前にもう一度どのようにシグネチャ部分が生成されるか表記する

ハッシュ関数(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

まずはbase64UrlEncode(header) + "." +base64UrlEncode(payload)
を求める


hp = base64.urlsafe_b64encode(header)+b'.'+base64.urlsafe_b64encode(payload)
print(hp)

変数hederには先程わかっているところで示したヘッダが入り,同様にpayloadにはペイロードが入る

実行結果は以下のようになるだろう

b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9'

次に第二引数と変数hpを使いシグネチャを作成する

secret = b'secret'
signature = hmac.new(secret,hp, hashlib.sha256)
signature = signature.digest()
signature = base64.urlsafe_b64encode(signature)
print(signature)

実行結果は以下のようになるだろう

b'qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o='

ここでほぼ一致していることがわかる
次に作成したものを結合していく

jwt = (hp+b"."+signature).decode("UTF-8").strip("=")
print(jwt)

.で結合していることがわかる
ついでに文字列にしてbase64のパディングを削除している

そうすると実行結果は以下のようになるだろう

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o

これを先程の結果と比べると一致していることがわかるだろう

まとめ

ここでもう一度全体のコードを表記する
まずはディレクトリ構成

.
|-- main.go
`-- main.py

のようになっている
次にgolangでjwt-goにてjwtの発行を行ったmain.go
main.go

package main

import (
    "fmt"
    "github.com/dgrijalva/jwt-go"
)

type jwtCustomClaims struct {
    Name  string `json:"name"`
    Admin bool   `json:"admin"`
    jwt.StandardClaims
}

func main() {
    claims := &jwtCustomClaims{
        "test",
        true,
        jwt.StandardClaims{
            ExpiresAt: 1539762311, //有効期限をを発行しておく
        },
    }
    j := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Secretで文字列にする. このSecretはサーバだけが知っている

    token, _ := j.SignedString([]byte("secret")) //学習用なのでerr処理は飛ばす

    fmt.Print(token)
}

実行するとjwtが発行される

$ go run main.go
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o

次に自力でjwtを発行したmain.py
main.py

import sys,re
import base64
import json
import hashlib,hmac
import urllib.request

args = sys.argv
str = args[1].split('.')

print(args[1])
header = base64.b64decode(str[0])
payload = base64.b64decode(str[1])
print(header)
print(payload)

hp = base64.urlsafe_b64encode(header)+b'.'+base64.urlsafe_b64encode(payload)
print(hp)

secret = b'secret'
signature = hmac.new(secret,hp, hashlib.sha256)
signature = signature.digest()
signature = base64.urlsafe_b64encode(signature)


jwt = (hp+b"."+signature).decode("UTF-8").strip("=")
print(jwt)

おすすめする実行の仕方は

$ python main.py $(go run main.go)

そうすると
実行結果は

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o
b'{"alg":"HS256","typ":"JWT"}'
b'{"name":"test","admin":true,"exp":1539762311}'
b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9'
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o

1行目はmain.goで発行したもの
5行目はpythonで作成したもの
一致していることがわかる

ありがとうございました

参考

jwt.io
echo.labstack.com

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5