Сообщение об ошибке
Signature verification failed
PyJWT (Python) — jwt.exceptions.InvalidSignatureError
Что это означает
PyJWT разобрал структуру токена и выполнил проверку подписи, но она не прошла: подпись не соответствует ключу и полезной нагрузке. InvalidSignatureError — подкласс DecodeError, поэтому слишком широкий except DecodeError может выдать эту ошибку за проблему разбора, хотя на самом деле ключ не соответствует содержимому. Для токенов ES256 есть ещё одна тонкость: подпись JWS должна быть в формате raw r‖s длиной 64 байта, поэтому токены, выпущенные самописным кодом подписи, который выдавал ASN.1 DER, не проходят здесь проверку, даже если ключ правильный.
Почему это происходит
Ключ не тот, которым подписывал издатель
частоРотация пары ключей, после которой у проверяющей стороны остался старый открытый ключ, скопированный по ошибке не тот PEM или два сервиса, которые читают из менеджера секретов разные версии секрета. У издателей, публикующих ключи через JWKS, к тому же результату приводит несовпадение kid.
Самописный код подписи выдал неправильную подпись
иногдаПомимо DER вместо raw r‖s, самописный код подписи может хешировать SHA-384 при заголовке ES256 или подписывать JSON вместо текста header.payload в base64url — и тогда получается корректно сформированная подпись, которая никогда не проходит проверку. Ключ, повреждённый при передаче через конфигурацию, к этой ошибке не приводит: PEM с буквальными последовательностями \n не загружается, и возникает ошибка «Unable to load PEM file», а кавычки или отступы вокруг неповреждённого PEM не мешают.
Токен изменили после подписания
иногдаПромежуточное ПО (middleware), которое заново сериализует JSON полезной нагрузки или меняет хотя бы один символ, меняет подписанные байты. Подпись вычисляется над точным текстом в base64url — косметических правок здесь не бывает. (Добавленное дополнение base64 отклоняется раньше, с ошибкой «Invalid payload padding», а обрезанный токен — с ошибкой «Not enough segments».)
Как исправить
- 1.
Вычислите отпечаток ключа на обеих сторонах
Выведите отпечаток SHA-256 открытого ключа, который загрузила проверяющая сторона, и сравните его с отпечатком ключа издателя. Если они различаются, причина найдена; если совпадают, проблему нужно искать в байтах токена.
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.
Проверьте kid, прежде чем подозревать повреждение
Прочитайте заголовок без проверки и убедитесь, что проверяете ключом, который указан в токене. Если у издателя несколько ключей, выбирайте нужный по kid через 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")