pyjwt: RS256 Token Validation & Decoding using Public Key Not Working "ValueError: Could not deserialize key data."
I’m trying to validate Google’s ID Tokens for user authentication on a web app. The id token can be decoded fine if I disable verification, but won’t verify when I pass it the RSA256 Public Key. The Public Key in question is Base64urlUInt-Encoded (RFC 7518 Specification).
The Entire Public Key Response
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "8c9eb968f73744eaed421e48010142bce51a067f",
"n": "uweJ3hFY9wqZ6ZG-iSNhQwHtKCGl8G_jcQgGPjOrS-Rum3dyDjicqkAyfS8XDn480KD_TZ5m-lqBjqfimePu2_cH4URDPIwsqSzJI2_piEhaqnXRptIe5YB5imAL6iETKaOPjw284Fc7EdHK-ekHMn3AXjsy9AIErwAVw4-4ZXXwHbyQXJy1DyUB4ZzxiEvw_qkQmLdltmrNkLOw-Xh-C9UkTZ9NA58bYPBnxLwnAu_ggw_g_-hCAs6OvXZbAfFHhIGBLyjtdDLVrfXo1112QREB9d5sEds0bKZtJcD9afl4E7Ht6G-g3jNP2clAu6-6B-cIe4-j8Ph1uJDPkAmDfw",
"e": "AQAB"
}
According to the Specification I linked above, the “n” parameter is the Modulus, and the “e” parameter is the exponent. I’ve tried absolutely every combination of decoding these to common Base64 format, but no matter what I do, pyJWT doesn’t like it.
Expected Result
A Verified, decoded JSON data packet.
Actual Result
Traceback (most recent call last):
File "/anaconda3/lib/python3.6/site-packages/jwt/algorithms.py", line 205, in prepare_key
key = load_pem_private_key(key, password=None, backend=default_backend())
File "/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/primitives/serialization.py", line 20, in load_pem_private_key
return backend.load_pem_private_key(data, password)
File "/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1015, in load_pem_private_key
password,
File "/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1234, in _load_key
self._handle_key_loading_error()
File "/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1292, in _handle_key_loading_error
raise ValueError("Could not deserialize key data.")
ValueError: Could not deserialize key data.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/anaconda3/lib/python3.6/site-packages/jwt/api_jwt.py", line 93, in decode
jwt, key=key, algorithms=algorithms, options=options, **kwargs
File "/anaconda3/lib/python3.6/site-packages/jwt/api_jws.py", line 157, in decode
key, algorithms)
File "/anaconda3/lib/python3.6/site-packages/jwt/api_jws.py", line 221, in _verify_signature
key = alg_obj.prepare_key(key)
File "/anaconda3/lib/python3.6/site-packages/jwt/algorithms.py", line 207, in prepare_key
key = load_pem_public_key(key, backend=default_backend())
File "/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/primitives/serialization.py", line 24, in load_pem_public_key
return backend.load_pem_public_key(data)
File "/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1041, in load_pem_public_key
self._handle_key_loading_error()
File "/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1292, in _handle_key_loading_error
raise ValueError("Could not deserialize key data.")
ValueError: Could not deserialize key data.
Reproduction Steps
import jwt
IDjwt = <my id_token here> # Decoding this with verify=False works correctly, so the problem isn't with the ID Token
GoogPubKey = b'uweJ3hFY9wqZ6ZG-iSNhQwHtKCGl8G_jcQgGPjOrS-Rum3dyDjicqkAyfS8XDn480KD_TZ5m-lqBjqfimePu2_cH4URDPIwsqSzJI2_piEhaqnXRptIe5YB5imAL6iETKaOPjw284Fc7EdHK-ekHMn3AXjsy9AIErwAVw4-4ZXXwHbyQXJy1DyUB4ZzxiEvw_qkQmLdltmrNkLOw-Xh-C9UkTZ9NA58bYPBnxLwnAu_ggw_g_-hCAs6OvXZbAfFHhIGBLyjtdDLVrfXo1112QREB9d5sEds0bKZtJcD9afl4E7Ht6G-g3jNP2clAu6-6B-cIe4-j8Ph1uJDPkAmDfw'
#Convert to Base64 (replace '-', '_' with '+', '/', respectively, and pad with '=' to make multiple of 4)
GoogPubKey = GoogPubKey.replace(b'-', b'+')
GoogPubKey = GoogPubKey.replace(b'_', b'/')
GoogPubKey += b'=='
len(GoogPubKey) % 4 # 0
GoogPubKey = b'-----BEGIN PUBLIC KEY-----\n' + GoogPubKey + b'\n-----END PUBLIC KEY-----'
decoded = jwt.decode(IDjwt, GoogPubKey, algorithms='RS256')
Again, I’ve tried over a dozen different ways of using this RSA Public Key Google supplies, but nothing works (Using Base64url like they provide, using or not using the ‘BEGIN PUBLIC KEY’ prefix/suffix, type bytes or str, adding the “AQAB” prefix in multiple places, nothing works)
Any help would be greatly appreciated!! Thank you
System Information
$ python -m jwt.help
{
"cryptography": {
"version": "2.1.4"
},
"implementation": {
"name": "CPython",
"version": "3.6.4"
},
"platform": {
"release": "17.4.0",
"system": "Darwin"
},
"pyjwt": {
"version": "1.6.4"
}
}
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 13
- Comments: 19 (2 by maintainers)
Try something like this:
just fyi, a simpler approach also worked for me;
@jpadilla Out of 1000’s of messages i’ve read today, your hint WORKED.
@tyrelkostyk give up on passing public key yourself. Instead realy on OIDC well known config to discover exactly how you can construct RSA from JWK. It is there you don’t have to guess it, at least it worked for me. Here is how I retrieved it.
Find your well-known config endpoint that lists all other endpoints. In case of keycloak its here (obviously realm will be different)
…then load “certs” endpoint
and then… use @jpadilla example above but with new token and detected jwk construction set
TADA…
Thank you!!!
Just a note that I faced a similar issue and found a solution. In my case, I was using a signing certificate downloaded from Auth0 in PEM format, which I had incorrectly assumed was a public key. According to https://cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/#pem:
My PEM file did start with
-----BEGIN CERTIFICATE-----
, so I had to implement what is suggested by the cryptograpy library. Here’s some sample code for doing this:Note that in this code,
settings.AUTH0_PUBLIC_CERTIFICATE
is abytes
value containing the content of the PEM key.Now that I’m actually passing a public key into
jwt.decode
, everything works correctly.See also: https://community.auth0.com/t/token-validation-with-python/21589/2.
The same issue.
FYI:
Openidc well.known endpoint for Azure: doc: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc
https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration
@tyrelkostyk and for Google: doc: https://developers.google.com/identity/protocols/OpenIDConnect#discovery
https://accounts.google.com/.well-known/openid-configuration
@scottwn At least, you should use
public
instead ofkey
.decoded = jwt.decode(token, public, algorithms=key[‘alg’])
This was exactly what i needed. i also struggled with keycloak. i had to make a small change to the jwt.decode though to make it working on my side:
decoded = jwt.decode(token, public_key, algorithms="RS256", audience="account")
Thanks alot for this solution!
@vjsimha1 Indeed, this is the solution I came up with for decoding AWS Cognito tokens:
Does this look similar to your solution?
I could get this working without a public/private key using options.
Note: “verify_signature”: False is not recommended for production use.
same problem when trying to validate firebase tokens with the public key https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com
works only with verify=False
` @app.post(“/auth_firebase”) async def route_firebase_login_access_token(form_data: OAuth2FirebaseTokenRequestForm = Depends()): token = form_data.token
` the token test: http://jwt.io
Just for reference, I’m also using keycloak and for me the issue was line breaks (windows vs linux) and adding linebreaks (after 76 characters) in the keystring (besides adding the header and footer)