使用 ES256 的 JWT 签名无效

Invalid JWT signature with ES256

我正在尝试手动创建 ES256 JWT 令牌。我有一个用 python 编写的小脚本,它签署了一个使用 ecdsa-python. But the signature is invalid on jwt.io.

的 sha256 哈希

重现步骤:

  1. 创建 base64 header + 负载:

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

  1. 从 base64 header + 有效负载创建 SHA256 哈希:

FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F

  1. 生成 ec_private 密钥:

openssl ec -in ec_private.pem -noout -text

  1. 使用小 python 程序对 SHA256 哈希进行 ecdsa 签名
from json import dumps
from ellipticcurve.ecdsa import Ecdsa
from ellipticcurve.privateKey import PrivateKey

import base64

def toBase64Url(input):
    return input.replace("+", "-").replace("/", "_").rstrip("=")

# Generate privateKey from PEM string
privateKey = PrivateKey.fromPem("""
    -----BEGIN EC PARAMETERS-----
    BgUrgQQACg==
    -----END EC PARAMETERS-----
    -----BEGIN EC PRIVATE KEY-----
    MHcCAQEEIJfChy9fKFItzqcb8DKBm+2oH0YTZ7N61SQpyABgVZANoAoGCCqGSM49
    AwEHoUQDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0uoYcvX4Z7ROUIgYRvgfpsjBa
    Iv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
    -----END EC PRIVATE KEY-----
""")

# Create message from json
message = "FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F"

signature = Ecdsa.sign(message, privateKey)

# Generate Signature in base64. This result can be sent to Stark Bank in the request header as the Digital-Signature parameter.
print("Base64: " + signature.toBase64())
print("Base64Url: " +toBase64Url(signature.toBase64()))



# To double check if the message matches the signature, do this:
publicKey = privateKey.publicKey()

print("Hash verification succesfull: " + str(Ecdsa.verify(message, signature, publicKey)))

输出:

Base64: MEQCIFyP4IoZGhzGfDCPX6fVxjtB+nrXDVhOTQwdc5vu8z4eAiBNalfGHqdaO3nCmTqimpAHF+IHzxk8em+OMMHrJkPOhA==

Base64Url: MEQCIFyP4IoZGhzGfDCPX6fVxjtB-nrXDVhOTQwdc5vu8z4eAiBNalfGHqdaO3nCmTqimpAHF-IHzxk8em-OMMHrJkPOhA

Hash verification succesfull: True

  1. 检查jwt.io中的签名给出无效签名

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.MEQCIFyP4IoZGhzGfDCPX6fVxjtB-nrXDVhOTQwdc5vu8z4eAiBNalfGHqdaO3nCmTqimpAHF-IHzxk8em-OMMHrJkPOhA

键:

Public:

-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0 uoYcvX4Z7ROUIgYRvgfpsjBaIv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ== -----END PUBLIC KEY-----

私人:

-----BEGIN EC PRIVATE KEY----- MHcCAQEEIJfChy9fKFItzqcb8DKBm+2oH0YTZ7N61SQpyABgVZANoAoGCCqGSM49 AwEHoUQDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0uoYcvX4Z7ROUIgYRvgfpsjBa Iv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ== -----END EC PRIVATE KEY-----

我知道有很多 jwt 签名 python 库,但使用它是为了了解如何创建 jwt 令牌。

编辑:

正如@Topaco 所指出的,该库使用曲线 secp256k1 而不是 secp256r1。 secp256r1 | prime256v1 | NIST P-256 都是不同标准组织为同一条曲线取的不同名称(Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)). I changed the library to python-ecdsa 代号为:

from ecdsa import SigningKey, NIST256p
import base64

def toBase64Url(input):
    return input.replace("+", "-").replace("/", "_").rstrip("=")


sk = SigningKey.from_pem("""
    -----BEGIN EC PRIVATE KEY-----
    MHcCAQEEIJfChy9fKFItzqcb8DKBm+2oH0YTZ7N61SQpyABgVZANoAoGCCqGSM49
    AwEHoUQDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0uoYcvX4Z7ROUIgYRvgfpsjBa
    Iv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
    -----END EC PRIVATE KEY-----
""")
vk = VerifyingKey.from_pem("""
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0
uoYcvX4Z7ROUIgYRvgfpsjBaIv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END PUBLIC KEY-----
""")
signature = sk.sign(b"FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F")

print(base64.b64encode(signature))
print("Base64: " + base64.b64encode(signature).decode("utf-8"))
print("Base64Url: " + toBase64Url(base64.b64encode(signature).decode("utf-8")))

assert vk.verify(signature, b"FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F")
print("Hash verification succesfull: " + str(vk.verify(signature, b"FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F")))

输出:

Base64: rMBgC0ismGdd5rd7n1L+LDsQ2UO5+cjBwPNYh+xBZvO6fKoJIfmfyNpxw+kxmyKWlK+55dF5eMH1u327DMJvvA==

Base64Url: rMBgC0ismGdd5rd7n1L-LDsQ2UO5-cjBwPNYh-xBZvO6fKoJIfmfyNpxw-kxmyKWlK-55dF5eMH1u327DMJvvA

Hash verification succesfull: True

但是签名还是无效

您正在隐式使用哈希的库,默认应用 SHA1。 IE。为了与 ES256 兼容,必须明确指定 SHA256,并且必须使用未散列的 JWT,例如:

from ecdsa import SigningKey, VerifyingKey
import base64
from hashlib import sha256

def toBase64Url(input):
    return input.replace("+", "-").replace("/", "_").rstrip("=")

jwt = b"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0"

sk = SigningKey.from_pem("""
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJfChy9fKFItzqcb8DKBm+2oH0YTZ7N61SQpyABgVZANoAoGCCqGSM49
AwEHoUQDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0uoYcvX4Z7ROUIgYRvgfpsjBa
Iv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END EC PRIVATE KEY-----
""")
vk = VerifyingKey.from_pem("""
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0
uoYcvX4Z7ROUIgYRvgfpsjBaIv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END PUBLIC KEY-----
""")

signature = sk.sign(jwt, hashfunc=sha256)

print("Base64: " + base64.b64encode(signature).decode("utf-8"))
print("Base64Url: " + toBase64Url(base64.b64encode(signature).decode("utf-8")))

assert vk.verify(signature, jwt, hashfunc=sha256)
print("Hash verification succesfull: " + str(vk.verify(signature, jwt, hashfunc=sha256)))

可能的输出是:

Base64: Mr4/DF87ek66E2GcAc+2H3ulHplCnxygz65h9dkdvm8QsZBbm2N5EjIgyiWsynza9zCGjjnzBUiXYvij9LLikA==
Base64Url: Mr4_DF87ek66E2GcAc-2H3ulHplCnxygz65h9dkdvm8QsZBbm2N5EjIgyiWsynza9zCGjjnzBUiXYvij9LLikA
Hash verification succesfull: True

生成的签名令牌

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.Mr4_DF87ek66E2GcAc-2H3ulHplCnxygz65h9dkdvm8QsZBbm2N5EjIgyiWsynza9zCGjjnzBUiXYvij9LLikA

然后可以使用此处使用的 public 密钥在 https://jwt.io/ 上成功验证。