Error message
The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
Also printed as
- jwt.exceptions.InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
PyJWT (Python) — jwt.exceptions.InvalidKeyError from HMACAlgorithm.prepare_key
What it means
PyJWT was asked to use a PEM public key, private key or certificate as the secret for an HMAC algorithm (HS256/384/512), and refused. This guard exists because of a well-known class of JWT bugs: if a verifier accepts both RS/ES and HS algorithms with the same key argument, anyone who knows the public key could compute a valid HMAC over a forged token. Here the token's header named an HMAC algorithm that your allow-list permits.
Why it happens
The allow-list mixes HMAC and asymmetric algorithms
common`algorithms=["RS256", "HS256"]` with a public key means an HS256 token tries to use that public key as an HMAC secret. Accepting it would let a forged token pass; PyJWT stops it.
The service is configured for HS256 but holds a PEM
occasionalA configuration that says `JWT_ALGORITHM=HS256` while `JWT_KEY` contains a PEM file. Decide which scheme the issuer really uses and make both values agree.
Someone is testing the verifier
occasionalTokens with `alg: "HS256"` arriving at an API whose issuer signs with RSA or ECDSA are a classic probe for the algorithm-confusion weakness. Log the source; the error means the probe failed.
How to fix it
- 1.
Allow one algorithm family per key
Pass only the asymmetric algorithms your issuer uses together with its public key, and keep HMAC verification (if you need it at all) on a separate code path with its own random secret.
python # Asymmetric issuer: allow only asymmetric algorithms with the public key. claims = jwt.decode(token, public_key, algorithms=["ES256"], audience="https://api.example.com") # Shared-secret tokens: a random secret, never a PEM, and only HMAC algorithms. claims = jwt.decode(token, HMAC_SECRET, algorithms=["HS256"], audience="internal")