ラベル jwt の投稿を表示しています。 すべての投稿を表示
ラベル jwt の投稿を表示しています。 すべての投稿を表示

2022年3月3日木曜日

pyjwt で jwk を使って認証情報を取得しそこから jwt を復号化する方法

pyjwt で jwk を使って認証情報を取得しそこから jwt を復号化する方法

概要

jwk は鍵を公開鍵管理する API で JSON 形式で取得できます
今回は jwk で取得した鍵情報を使用してそこから jwt を復号化する方法を紹介します
なお暗号化方式は ES512 で楕円曲線演算方法は P-521 の場合になります

鍵などの情報は適当に記載しています

環境

  • macOS 11.6.4
  • Python 3.10.2
  • pyjwt 2.3.0

サンプルコード

import jwt
import json

from jwt.algorithms import ECAlgorithm
from cryptography.hazmat.primitives import serialization

jwks = '{"keys":[{"alg":"ES512","crv":"P-521","kid":"key_id1","kty":"EC","use":"sig","x":"66_bytes_characters","y":"65_or_66_bytes_characters"}]}'
jwks_dict = json.loads(jwks)
jwt_token = "encripted_json_web_token"

# トークンから kid や alg などのヘッダ情報を取得
header = jwt.get_unverified_header(jwt_token)
# print(header)

# 公開鍵の一覧から kid に一致する jwk を取得
jwk_json = [jwk for jwk in jwks_dict['keys'] if jwk['kid'] == header['kid']]

# 公開鍵情報から暗号化するための pem キーを生成
public_key = ECAlgorithm.from_jwk(jwk_json[0])
pem = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)

# pem キーを使って jwt トークンを復号化する
decoded_jwt = jwt.decode(jwt_token, pem, algorithms=jwk_json[0]['alg'])
print(decoded_jwt)

軽く説明

まず jwk が公開してる鍵情報の JSON を jwks に設定しています
鍵が複数ある場合もあるので今回は複数の鍵を想定しています

まずトークン情報から jwt.get_unverified_header を使ってヘッダ情報を取得します
ここに暗号化した鍵の kid やアルゴリズムが記載されています

次に kid を元に公開鍵の一覧から復号化するための jwk を取得します
ヘッダに含まれる kid を使って合致する公開鍵を設定します

取得した jwk から PEM キーを生成します
生成するには ECAlgorithm.from_jwk を使います

あとは jwt.decode を使ってトークンをデコードします

pyjwt にパッチを当てる必要がある

実は pyjwt をそのまま使った場合だと動作しない場合があります
jwk で公開している楕円曲線演算方法が P-521 の場合 Y の鍵が 66 bytes である必要があります
しかし実際は 65 or 64 bytes (64bytes である可能性は 0.2%) になる可能性があり pyjwt では 65 or 64 bytes のときを考慮しておりません

なので以下のように条件を変更するパッチを当てる必要があります

  • vim /path/to/venv/site-packages/jwt/algorithms.py
elif curve == "P-521":
    if len(x) == 66 and (len(y) == 66 or len(y) == 65):
        curve_obj = ec.SECP521R1()
    else:
        raise InvalidKeyError("Coords should be 66 bytes for curve P-521")

最後に

jwk は基本的に https 経由で取得することになると思います
jwt も REST などの API 経由のレスポンスで取得することになると思います

pyjwt は修正が必要なので別の jwt ライブラリを使えばパッチを必要はないかもしれません (jose)

参考サイト

2022年3月2日水曜日

Python で jwt を試してみた

Python で jwt を試してみた

概要

過去に Ruby で jwt で試しました
今回は Python で試してみました

準備

  • pipenv install pyjwt

サンプルコード

import jwt

# 文字列 -> json web token
encoded_jwt = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
print(encoded_jwt)

# json web token -> 文字列
decoded_jwt = jwt.decode(encoded_jwt, "secret", algorithms=["HS256"])
print(decoded_jwt)

鍵を使った場合

別途 cryptography が必要になります

  • pipenv install cryptography

pem 形式の鍵をテスト用に作成します

  • ssh-keygen -t rsa -b 4096 -m PEM -f id_rsa
  • openssl rsa -in id_rsa -pubout -outform PEM -out id_rsa.pub
import jwt

priv_key_path = "id_rsa"
pub_key_path = "id_rsa.pub"
encoded_jwt = ""

with open(priv_key_path) as private_key:
    encoded_jwt = jwt.encode({"some": "payload"}, private_key.read(), algorithm="RS256")
    print(encoded_jwt)

with open(pub_key_path) as public_key:
    decoded_jwt = jwt.decode(encoded_jwt, public_key.read(), algorithms=["RS256"])
    print(decoded_jwt)

最後に

簡単に使えます
その他オプションでヘッダなども付与することができます

参考サイト

2021年4月2日金曜日

Ruby で jwt を使ってみた

Ruby で jwt を使ってみた

概要

jwt (JSON Web Token) は JSON の情報をあるアルゴリズムで暗号化し送受信する仕組みです
トークンの送信するだけなので比較的に簡単に使えます
認証などに使われたりします
今回は Ruby で jwt を使ってトークンの生成や復号化を行ってみました

環境

  • Ruby 3.0.0
    • jwt 2.0.10

インストール

  • bundle init
  • vim Gemfile
gem "jwt"
  • bundle install

Secret パスワードを使って暗号化/復号化

パスワードを使って暗号化と復号化を行います
送信側と受信側で同じでシークレットを共有する必要があります
使用できるアルゴリズムは「HS256」「HS512256」「HS384」「HS512」になります
RbNaCl を使うと更に暗号化の種類を増やせます

  • vim test.rb
require "jwt"

payload = {
  "data" => "test"
}
hmac_secret = 'my$ecretK3y'
alg = "HS512"

token = JWT.encode payload, hmac_secret, alg
puts token

decoded_token = JWT.decode token, hmac_secret, true, { algorithm: alg }
puts decoded_token

公開鍵認証 (RSA) を使って暗号化/復号化

公開鍵を使って暗号化と復号化を行います
暗号化する側で秘密鍵を使い復号化する側で公開鍵を使います
使用できるアルゴリズムは「RS256」「RS384」「RS512」になります

  • openssl genrsa > priv.key
  • openssl rsa -pubout < priv.key > pub.key
  • vim test.rb
require "jwt"
require "openssl"

payload = {
  "data" => "test"
}
OpenSSL::PKey::RSA.new 2048
pri_key = OpenSSL::PKey::RSA.new File.read("./priv.key")
pub_key = pri_key.public_key
alg = "RS512"

token = JWT.encode payload, pri_key, alg
puts token

decoded_token = JWT.decode token, pub_key, true, { algorithm: alg }
puts decoded_token

最後に

他にもいろいろな暗号化方式を使ったトークンの生成方法があります
シークレットの共有方式よりも鍵を使った方式のほうがセキュアな気はします

参考サイト