How to verify a JWT using python PyJWT with public key
我一直在努力让PyJWT 1.1.0验证具有公共密钥的JWT。这些密钥是Keycloak随附的默认值。该问题很可能与秘密密钥的创建有关,但是我还没有找到任何有效的示例来创建没有私钥和公钥的证书。
这是我尝试使其运行的尝试。下面的一些测试抱怨密钥无效,而有些测试则抱怨令牌没有针对密钥正确验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 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-----\ " + secret +"\ -----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实现兼容,请参见下文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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-----\ " + key +"\ -----END PUBLIC KEY-----"; SignatureVerifier verifier = new RsaVerifier(verifierKey); System.out.println(JwtHelper.decodeAndVerify(token, verifier)); } } |
更新:我可以使用以下代码使用HS256(已通过http://jwt.io/验证)正确地签署令牌。但是,我无法使用PyJWT解码PyJWT签名令牌。界面真的很奇怪。以下示例(秘密与上述示例相同):
1 2 3 4 | 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:此方法有效
1 2 3 4 5 6 7 8 | 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) |
,但是不起作用
1 2 3 4 5 6 | 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) |
我要把它放在这里,找下一个像我这样的人。
我需要的是:
设置:
1 2 3 4 5 6 7 8 | # 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 |
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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-以及如何快速运行此示例的docker示例
@ javier-buzzi的答案向我返回了此错误:
1 | TypeError: from_buffer() cannot return the address of a unicode object |
这是我设法使其与python-jose
一起使用的方法创建一个RSA证书(auth.pem )和公钥(auth.pub):
1 2 | openssl genpkey -out auth.pem -algorithm rsa -pkeyopt rsa_keygen_bits:2048 openssl rsa -in auth.pem -out auth.pub -pubout |
(感谢哈维尔)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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) |
输出:
1 2 | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzYW1wbGUiOiJkYXRhIn0.GnDlS0FRFqdk1CsqFg2adHwSvrL8_JKtk4IQpuAzbjdDIi1xoymxxMIW4QNhl67QHIQrs0NG6lBi7eNfJ69Kgu6j-bY4NVP5-0D03wDrlBNowBPLMQ7RoCiDvtN1gqaTdf6VyNju6m9FmGImneZ84XMX2d1yWzXMSGtL2_8e99BmK0-h3r_o8IF7eSHN1SVxqrIN7vpcgfKcG0QjLZ-kBFpq4kgj5Fcr5coBIMmK6O0jB_4lBsNGa_0GixCXeWXkv_KqAky2yliEzV68lHOBCsBN_ZAjB3kllaIAOJCsQPLdqgXqgpeMQdzktVCVJKMAEYPdlv8mdadJSvxwxT9HBA {'sample': 'data'} |
另一个库(python-jose)可能有助于验证。
请注意,密钥必须是要传递给
您可以使用pyjwkest提取令牌并进行验证:
1 | pip install pyjwkest |
大多数代码来自此处:https://github.com/ByteInternet/drf-oidc-auth/blob/ master / oidc_auth / authentication.py进行了一些简化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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() |
现在使用以下命令检查令牌:
1 2 | if __name__ == '__main__': TokenChecker().check_token(token='your-jwt-token') |