web-dev-qa-db-ja.com

python PyJWT with public keyを使用してJWTを確認する方法

PyJWT 1.1.0が公開鍵でJWTを検証するのに苦労しました。これらのキーは、Keycloakに付属するデフォルトです。おそらく問題は秘密鍵の作成に関連していますが、秘密鍵と公開鍵の両方を含む証明書なしで鍵を作成するための実用的な例は見つかりませんでした。

これを機能させるための私の試みを以下に示します。以下のテストのいくつかは無効なキーについて不平を言い、それらのいくつかはトークンがキーに対して適切に検証されないという不平を言います。

import jwt

from cryptography.hazmat.backends import default_backend
from itsdangerous import base64_decode
from Crypto.PublicKey import RSA


secret = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIE6a1NyEFe7qCDFrvWFZiAlY1ttE5596w5dLjNSaHlKGv8AXbKg/f8yKY9fKAJ5BKoeWEkPPjpn1t9QQAZYzqH9KNOFigMU8pSaRUxjI2dDvwmu8ZH6EExY+RfrPjQGmeliK18iFzFgBtf0eH3NAW3Pf71OZZz+cuNnVtE9lrYQIDAQAB"
secretDer = base64_decode(secret)
sshrsaSecret = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCIE6a1NyEFe7qCDFrvWFZiAlY1ttE5596w5dLjNSaHlKGv8AXbKg/f8yKY9fKAJ5BKoeWEkPPjpn1t9QQAZYzqH9KNOFigMU8pSaRUxjI2dDvwmu8ZH6EExY+RfrPjQGmeliK18iFzFgBtf0eH3NAW3Pf71OZZz+cuNnVtE9lrYQ=="
secretPEM = "-----BEGIN PUBLIC KEY-----\n" + secret + "\n-----END PUBLIC KEY-----"
access_token = "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiIzM2ZhZGYzMS04MzZmLTQzYWUtODM4MS01OGJhM2RhMDMwYTciLCJleHAiOjE0MjkwNzYyNTYsIm5iZiI6MCwiaWF0IjoxNDI5MDc2MTk2LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODEvYXV0aC9yZWFsbXMvbWFzdGVyIiwiYXVkIjoic2VjdXJpdHktYWRtaW4tY29uc29sZSIsInN1YiI6ImMzNWJlODAyLTcyOGUtNGMyNC1iMjQ1LTQxMWIwMDRmZTc2NSIsImF6cCI6InNlY3VyaXR5LWFkbWluLWNvbnNvbGUiLCJzZXNzaW9uX3N0YXRlIjoiYmRjOGM0ZDgtYzUwNy00MDQ2LWE4NDctYmRlY2QxNDVmZTNiIiwiY2xpZW50X3Nlc3Npb24iOiI0OTI5YmRjNi0xOWFhLTQ3MDYtYTU1Mi1lOWI0MGFhMDg5ZTYiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYWRtaW4iLCJjcmVhdGUtcmVhbG0iXX0sInJlc291cmNlX2FjY2VzcyI6eyJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1jbGllbnRzIiwidmlldy1yZWFsbSIsInZpZXctZXZlbnRzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS11c2VycyIsIm1hbmFnZS1yZWFsbSJdfX0sIm5hbWUiOiIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiJ9.O7e8dkv0k-2HCjMdZFXIxLhypVyRPwIdrQsYTMwC1996wbsjIw1L3OjDSzJKXcx0U9YrVeRM4yMVlFg40uJDC-9IsKZ8nr5dl_da8SzgpAkempxpas3girST2U9uvY56m2Spp6-EFInvMSb6k4t1L49_Q7R2g0DOlKzxgQd87LY"

############### Test using PEM key (with ----- lines)
try:
    access_token_json = jwt.decode(access_token, key=secretPEM)
except Exception as e:
    print "Not working using PEM key with ----: ", e
else:
    print "It worked!"

############### Test using PEM key (without ----- lines)
try:
    access_token_json = jwt.decode(access_token, key=secret)
except Exception as e:
    print "Not working using PEM key without ----: ", e
else:
    print "It worked!"

############### Test using DER key
try:
    access_token_json = jwt.decode(access_token, key=secretDer)
except Exception as e:
    print "Not working using DER key: ", e
else:
    print "It worked!"

############### Test using DER key #2
try:
    public_key = default_backend().load_der_public_key(secretDer)
    access_token_json = jwt.decode(access_token, key=public_key)
except Exception as e:
    print "Not working using DER key #2: ", e
else:
    print "It worked!"

############### Test using SSH style key
try:
    access_token_json = jwt.decode(access_token, key=sshrsaSecret)
except Exception as e:
    print "Not working using SSH style key: ", e
else:
    print "It worked!"

############### Test using RSA numbers
class Numbers:
    pass

numbers = Numbers()
public_key = RSA.importKey(secretDer)
numbers.e = public_key.key.e
numbers.n = public_key.key.n
# yet another way to generated valid key object
public_key = default_backend().load_rsa_public_numbers(numbers)
print public_key
try:
    access_token_json = jwt.decode(access_token, key=public_key)
except Exception as e:
    print "Not working using RSA numbers: ", e
else:
    print "It worked!"
###############

トークンとキーがJava実装で動作していることを確認しました。以下を参照してください。

import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;

public class JWTTest {
    public static final void main(String[] argv) {
        String token = "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiIzM2ZhZGYzMS04MzZmLTQzYWUtODM4MS01OGJhM2RhMDMwYTciLCJleHAiOjE0MjkwNzYyNTYsIm5iZiI6MCwiaWF0IjoxNDI5MDc2MTk2LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODEvYXV0aC9yZWFsbXMvbWFzdGVyIiwiYXVkIjoic2VjdXJpdHktYWRtaW4tY29uc29sZSIsInN1YiI6ImMzNWJlODAyLTcyOGUtNGMyNC1iMjQ1LTQxMWIwMDRmZTc2NSIsImF6cCI6InNlY3VyaXR5LWFkbWluLWNvbnNvbGUiLCJzZXNzaW9uX3N0YXRlIjoiYmRjOGM0ZDgtYzUwNy00MDQ2LWE4NDctYmRlY2QxNDVmZTNiIiwiY2xpZW50X3Nlc3Npb24iOiI0OTI5YmRjNi0xOWFhLTQ3MDYtYTU1Mi1lOWI0MGFhMDg5ZTYiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYWRtaW4iLCJjcmVhdGUtcmVhbG0iXX0sInJlc291cmNlX2FjY2VzcyI6eyJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1jbGllbnRzIiwidmlldy1yZWFsbSIsInZpZXctZXZlbnRzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS11c2VycyIsIm1hbmFnZS1yZWFsbSJdfX0sIm5hbWUiOiIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiJ9.O7e8dkv0k-2HCjMdZFXIxLhypVyRPwIdrQsYTMwC1996wbsjIw1L3OjDSzJKXcx0U9YrVeRM4yMVlFg40uJDC-9IsKZ8nr5dl_da8SzgpAkempxpas3girST2U9uvY56m2Spp6-EFInvMSb6k4t1L49_Q7R2g0DOlKzxgQd87LY";
        String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCHJUdDw1bPg/tZBY+kDDZZQnAp1mVr0CMyE+VzvJ+n2v6SHBdjjuWEw+LfLd69evg8ndr1RRPWZ1ryKgWS/NKTNqH+UhHkK9NToDucJI9Bi/scCpBps+/X/S7gZtcBMdfd4IB+LPCsP8v2RT/H9VjeCP4sWuqNwAMtCMyGr1Vw9wIDAQAB";
        String verifierKey = "-----BEGIN PUBLIC KEY-----\n" + key + "\n-----END PUBLIC KEY-----";
        SignatureVerifier verifier = new RsaVerifier(verifierKey);
        System.out.println(JwtHelper.decodeAndVerify(token, verifier));
    }
}

更新:次のコードを使用して、HS256( http://jwt.io/ で検証済み)でトークンに適切に署名できます。ただし、PyJWTを使用してPyJWT署名済みトークンをデコードできません。インターフェースは本当に奇妙です。この例(シークレットは上記の例と同じです):

some_token = jwt.encode(access_token_json, secret)
# verified some_token to be valid with jwt.io
# the code below does not validate the token correctly
jwt.decode(some_token, key=secret)

更新2:これは機能します

from jwt.algorithms import HMACAlgorithm, RSAAlgorithm
access_token_json = jwt.decode(access_token, verify=False)
algo = HMACAlgorithm(HMACAlgorithm.SHA256)
shakey = algo.prepare_key(secret)
testtoken = jwt.encode(access_token_json, key=shakey, algorithm='HS256')
options={'verify_exp': False,  # Skipping expiration date check
         'verify_aud': False } # Skipping audience check
print jwt.decode(testtoken, key=shakey, options=options)

ただし、これは

from jwt.algorithms import HMACAlgorithm, RSAAlgorithm
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
shakey = algo.prepare_key(sshrsaSecret)
options={'verify_exp': False,  # Skipping expiration date check
         'verify_aud': False } # Skipping audience check
print jwt.decode(access_token, key=shakey, options=options)
22
Perttu T

これを探している私のような次の人のためにここに置いています。

私が必要としたのは:

  1. 秘密キーサービスの背後に置き(AWS API GATEWAYと考えてください)、JWTトークンを安全に生成し、それらを下位のサービスに渡すことができます。
  2. A Public key that i can give to any of my micro services/anything else that can validate that the JWT token is valid WITHOUT knowing my Private key

セットアップ:

  # lets create a key to sign these tokens with
  openssl genpkey -out mykey.pem -algorithm rsa -pkeyopt rsa_keygen_bits:2048 
  # lets generate a public key for it...
  openssl rsa -in mykey.pem -out mykey.pub -pubout 
  # make another key so we can test that we cannot decode from it
  openssl genpkey -out notmykey.pem -algorithm rsa -pkeyopt rsa_keygen_bits:2048 
  # this is really the key we would be using to try to check the signature
  openssl rsa -in notmykey.pem -out notmykey.pub -pubout

コード:

import jwt

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

# Load the key we created
with open("mykey.pem", "rb") as key_file:
    private_key = serialization.load_pem_private_key(
        key_file.read(),
        password=None,
        backend=default_backend()
    )

# The data we're trying to pass along from place to place
data = {'user_id': 1}

# Lets create the JWT token -- this is a byte array, meant to be sent as an HTTP header
jwt_token = jwt.encode(data, key=private_key, algorithm='RS256')

print(f'data {data}')
print(f'jwt_token {jwt_token}')

# Load the public key to run another test...
with open("mykey.pub", "rb") as key_file:
    public_key = serialization.load_pem_public_key(
        key_file.read(),
        backend=default_backend()
    )

# This will prove that the derived public-from-private key is valid
print(f'decoded with public key (internal): {jwt.decode(jwt_token, private_key.public_key())}')
# This will prove that an external service consuming this JWT token can trust the token 
# because this is the only key it will have to validate the token.
print(f'decoded with public key (external): {jwt.decode(jwt_token, public_key)}')

# Lets load another public key to see if we can load the data successfuly
with open("notmykey.pub", "rb") as key_file:
    not_my_public_key = serialization.load_pem_public_key(
        key_file.read(),
        backend=default_backend()
    )

# THIS WILL FAIL!!!!!!!!!!!!!!!!!!!!!!!
# Finally, this will not work and cause an exception
print(f'decoded with another public key: {jwt.decode(jwt_token, not_my_public_key)}')

詳細はこちら: https://Gist.github.com/kingbuzzman/3912cc66896be0a06bf0eb23bb1e1999 -これをすばやく実行する方法のドッカーの例と一緒に

10
Javier Buzzi

@ javier-buzziの答えは私にこのエラーを返しました:

TypeError: from_buffer() cannot return the address of a unicode object

これが python-jose で動作させる方法です

RSA証明書(auth.pem)とその公開鍵(auth.pub)を作成します。

openssl genpkey -out auth.pem -algorithm rsa -pkeyopt rsa_keygen_bits:2048 
openssl rsa -in auth.pem -out auth.pub -pubout

(ハビエルに感謝)


from jose import jwt
data = {
    "sample" : "data"
}

# Encode data
with open("auth.pem") as key_file:
    token = jwt.encode(data, key=key_file.read(), algorithm='RS256')

print(token)

# Decode data with only he public key
with open("auth.pub") as pubkey_file:
    decoded_data = jwt.decode(token, key=pubkey_file.read(), algorithms='RS256')

print(decoded_data)

出力:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzYW1wbGUiOiJkYXRhIn0.GnDlS0FRFqdk1CsqFg2adHwSvrL8_JKtk4IQpuAzbjdDIi1xoymxxMIW4QNhl67QHIQrs0NG6lBi7eNfJ69Kgu6j-bY4NVP5-0D03wDrlBNowBPLMQ7RoCiDvtN1gqaTdf6VyNju6m9FmGImneZ84XMX2d1yWzXMSGtL2_8e99BmK0-h3r_o8IF7eSHN1SVxqrIN7vpcgfKcG0QjLZ-kBFpq4kgj5Fcr5coBIMmK6O0jB_4lBsNGa_0GixCXeWXkv_KqAky2yliEzV68lHOBCsBN_ZAjB3kllaIAOJCsQPLdqgXqgpeMQdzktVCVJKMAEYPdlv8mdadJSvxwxT9HBA
{'sample': 'data'}
2

これ 他のライブラリ(python-jose) は確認に役立ちます。

キーはJSON辞書である必要がありますdecodeに渡すことに注意してください。

1
Efren

Pyjwkestを使用してトークンを抽出し、確認できます。

pip install pyjwkest

_decode_tokenは、署名がトークンのコンテンツと一致するかどうかを検証しますが、有効期限、トークン発行者などの検証は行いません。

_validate_claims発行者と有効期限を確認します。

ほとんどのコードはここからです: https://github.com/ByteInternet/drf-oidc-auth/blob/master/oidc_auth/authentication.py 少し簡略化して。

import datetime
import logging
from calendar import timegm
from typing import Dict

import requests
from jwkest import JWKESTException
from jwkest.jwk import KEYS


class TokenChecker():
    def __init__(self):
        self.config_url: str = 'https://{your-oidc-provider}/auth/realms/{your-realm}/.well-known/openid-configuration/'
        self._load_config()
        self._load_jwks_data()

    def _load_config(self):
        # Loads issuer and jwks url (see method below)
        self.oidc_config: Dict = requests.get(self.config_url, verify=True).json()
        self.issuer = self.oidc_config['issuer']

    def _load_jwks_data(self):
        # jwks data contains the key you need to extract the token
        self.jwks_keys: KEYS = KEYS()
        self.jwks_keys.load_from_url(self.oidc_config['jwks_uri'])

    def _decode_token(self, token: str):
        try:
            self.id_token = JWS().verify_compact(token, keys=self.jwks_keys)
        except JWKESTException:
            logging.error('Invalid Authorization header. JWT Signature verification failed')

    def _validate_claims(self):
        if self.id_token.get('iss') != self.issuer:
            msg = 'Invalid Authorization header. Invalid JWT issuer.'
            logging.error(msg)

        # Check if token is expired
        utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple())
        if utc_timestamp > self.id_token.get('exp', 0):
            msg = 'Invalid Authorization header. JWT has expired.'
            logging.error(msg)
        if 'nbf' in self.id_token and utc_timestamp < self.id_token['nbf']:
            msg = 'Invalid Authorization header. JWT not yet valid.'
            logging.error(msg)

    def check_token(self, token: str):
        self._decode_token(token=token)
        self._validate_claims()

次にトークンを確認してください:

if __name__ == '__main__':
    TokenChecker().check_token(token='your-jwt-token')
0
Tobias Ernst