I'm trying to manually create an ES256 JWT token. I've a small script written in python which signs a sha256 hash which uses ecdsa-python. But the signature is invalid on jwt.io.
Steps to reproduce:
- Create base64 header + payload:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
- Create SHA256 hash from the base64 header + payload:
FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F
- Generate ec_private key:
openssl ec -in ec_private.pem -noout -text
- Use the small python program to ecdsa sign the SHA256 hash
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)))
The output:
Base64: MEQCIFyP4IoZGhzGfDCPX6fVxjtB+nrXDVhOTQwdc5vu8z4eAiBNalfGHqdaO3nCmTqimpAHF+IHzxk8em+OMMHrJkPOhA==
Base64Url: MEQCIFyP4IoZGhzGfDCPX6fVxjtB-nrXDVhOTQwdc5vu8z4eAiBNalfGHqdaO3nCmTqimpAHF-IHzxk8em-OMMHrJkPOhA
Hash verification succesfull: True
- Check the signature in jwt.io gives Invalid signature
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.MEQCIFyP4IoZGhzGfDCPX6fVxjtB-nrXDVhOTQwdc5vu8z4eAiBNalfGHqdaO3nCmTqimpAHF-IHzxk8em-OMMHrJkPOhA
Keys:
Public:
-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0 uoYcvX4Z7ROUIgYRvgfpsjBaIv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ== -----END PUBLIC KEY-----
Private:
-----BEGIN EC PRIVATE KEY----- MHcCAQEEIJfChy9fKFItzqcb8DKBm+2oH0YTZ7N61SQpyABgVZANoAoGCCqGSM49 AwEHoUQDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0uoYcvX4Z7ROUIgYRvgfpsjBa Iv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ== -----END EC PRIVATE KEY-----
I know that there are many jwt signing python libraries but the use of this is to understand how a jwt token is created.
EDIT:
As @Topaco pointed out this library uses curve secp256k1 instead of secp256r1. secp256r1 | prime256v1 | NIST P-256 are all different names chosen by different standards organizations for the same curve (Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)). I changed the library to python-ecdsa and the code to:
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")))
The output:
Base64: rMBgC0ismGdd5rd7n1L+LDsQ2UO5+cjBwPNYh+xBZvO6fKoJIfmfyNpxw+kxmyKWlK+55dF5eMH1u327DMJvvA==
Base64Url: rMBgC0ismGdd5rd7n1L-LDsQ2UO5-cjBwPNYh-xBZvO6fKoJIfmfyNpxw-kxmyKWlK-55dF5eMH1u327DMJvvA
Hash verification succesfull: True
But the signature is still invalid.