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

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

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

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

## JWTとは?
[以前書いた記事](https://qiita.com/aeleniumfor/items/1621897b97c8c8d44a94)を見ていただきたい

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

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

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

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


## jwt-goで作成してみる

main.go

```golang
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の結果は以下のようになると思う

```console
$ go run main.go
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o
```
何度か実行してみて常に同じ値が出されることを確認する

## pythonでパースしてみる

[jwt.io](https://jwt.io/)でパースしてもいいが今回は自力でパースする

main.py

```python
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)
```
実行の仕方は

```console
$ python main.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o 
```
長い.....ので

```console
$ 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 -> 下に書いた

 
```console
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o
```

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

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

```python
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)`
を求める

```python

hp = base64.urlsafe_b64encode(header)+b'.'+base64.urlsafe_b64encode(payload)
print(hp)
```
変数hederには先程わかっているところで示したヘッダが入り,同様にpayloadにはペイロードが入る

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

```
b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9'
```

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

```python
secret = b'secret'
signature = hmac.new(secret,hp, hashlib.sha256)
signature = signature.digest()
signature = base64.urlsafe_b64encode(signature)
print(signature)
```
実行結果は以下のようになるだろう

```
b'qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o='
```
ここでほぼ一致していることがわかる
次に作成したものを結合していく

```python
jwt = (hp+b"."+signature).decode("UTF-8").strip("=")
print(jwt)
```
`.`で結合していることがわかる
ついでに文字列にしてbase64のパディングを削除している

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

```console
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImFkbWluIjp0cnVlLCJleHAiOjE1Mzk3NjIzMTF9.qKfKFVcMSfM2_3qy01YUmOr4tO6uzDLNU4w-yXFye7o
```
これを先程の結果と比べると一致していることがわかるだろう

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

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

```golang
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が発行される

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

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

```python
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)
```
おすすめする実行の仕方は

```console
$ 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](https://jwt.io)
[echo.labstack.com](https://echo.labstack.com/cookbook/jwt)