Mensaje de error
Signature verification failed
PyJWT (Python) — jwt.exceptions.InvalidSignatureError
Qué significa
PyJWT decodificó la estructura del token y ejecutó la comprobación de la firma, que falló: la firma no corresponde a la clave y la carga útil. InvalidSignatureError es una subclase de DecodeError, así que un except DecodeError demasiado amplio puede etiquetar esto como un problema de análisis cuando en realidad la clave y el contenido no coinciden. Con los tokens ES256 hay una sutileza más: la firma JWS debe tener la forma r‖s en bruto de 64 bytes, así que los tokens generados por firmantes hechos a mano que emitían ASN.1 DER fallan aquí aunque la clave sea la correcta.
Por qué ocurre
Una clave distinta de la que usó el emisor
frecuenteUn par de claves rotado en el que el verificador conservó la clave pública antigua, un copiado y pegado del PEM equivocado o dos servicios que leen versiones distintas del secreto en el gestor de secretos. Con emisores basados en JWKS, un kid que no coincide tiene el mismo efecto.
Un firmante hecho a mano produjo una firma incorrecta
ocasionalAdemás del caso de DER en lugar de r‖s en bruto, un firmante casero que calcula el hash con SHA-384 bajo un encabezado ES256, o que firma el JSON en lugar del texto base64url header.payload, produce una firma bien formada que nunca se verifica. Una clave estropeada al pasar por la configuración no acaba aquí: un PEM con secuencias \n literales no llega a cargarse y falla con «Unable to load PEM file», y las comillas o la sangría alrededor de un PEM intacto se toleran.
El token se modificó después de firmarse
ocasionalUn middleware que vuelve a serializar el JSON de la carga útil o que cambia un solo carácter modifica los bytes firmados. La firma cubre el texto base64url exacto: los cambios cosméticos no son cosméticos. (El relleno base64 añadido se rechaza antes, como «Invalid payload padding», y un token cortado, como «Not enough segments».)
Cómo solucionarlo
- 1.
Calcular la huella digital de la clave en ambos lados
Imprime una huella digital SHA-256 de la clave pública que cargó el verificador y compárala con la del emisor. Si difieren, se acabó la búsqueda; si coinciden, el problema está en los bytes del token.
python import hashlib from cryptography.hazmat.primitives import serialization public_key = serialization.load_pem_public_key(pem_bytes) # the key the verifier uses spki = public_key.public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo) print(hashlib.sha256(spki).hexdigest()) # compare with the issuer's - 2.
Comprobar el kid antes de dar por hecho que el token está dañado
Lee el encabezado sin verificar y asegúrate de que verificas con la clave indicada en el token. Si el emisor tiene varias claves, resuelve la clave por kid mediante PyJWKClient.
python import jwt header = jwt.get_unverified_header(token) print(header) # {'alg': 'ES256', 'kid': '...'} from jwt import PyJWKClient key = PyJWKClient("https://issuer.example/.well-known/jwks.json") \ .get_signing_key_from_jwt(token).key payload = jwt.decode(token, key, algorithms=["ES256"], audience="api://orders")