aguiadouro
aguiadouro

Reputation: 133

JWT, how to verify signature?

I have this JWT:

eyJhbGciOiJSUzI1NiIsImtpZCI6IjVkMzQwZGRiYzNjNWJhY2M0Y2VlMWZiOWQxNmU5ODM3ZWM2MTYzZWIiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiemFnYWxvIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3Byb2ZlcHQtM2M0NzkiLCJhdWQiOiJwcm9mZXB0LTNjNDc5IiwiYXV0aF90aW1lIjoxNjY2MjkxNDAzLCJ1c2VyX2lkIjoiZ1JtdnFYb0tySE85T0RLUURCYTBWNnRaNTBLMiIsInN1YiI6ImdSbXZxWG9LckhPOU9ES1FEQmEwVjZ0WjUwSzIiLCJpYXQiOjE2NjYyOTE0MDMsImV4cCI6MTY2NjI5NTAwMywiZW1haWwiOiJyc2p1bGlhb0BnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicnNqdWxpYW9AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.ZkBqE8GCSGt9FX_LxoaLNgHcPx19EDMq3ARmZaJ_R1_FiBcQAp8T_AEmleVu68lqw7SdcM2aAjZ1kZbfkZ48hgfhW0LI03VC_6Dc4sq9pgCHWarteCeUz4fE1B6nl4nIbKI3nPQorKYTu82SXEzaRiEwHQCVayiMmnkjzj4d-2YVp4WA8If_h3jNHBe8giskjwkB2t6hB39vYLqvcM5sEeSBRpVT8zA-hmp2AeImcXagCK4Av7JIt_iBNuwT9dwMLtA6addoXcDYTJuRZ3GhVrbL8x_is9u2XDDLWDWdrj1yAjkq7pTPwC7KPft8Md2PKxqYR5bid_VRSjPIeb_k8A

And this Public Key:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3CTs4UeO1cS3FJAWDTcO
HHcAeEfZOCFmxju1xC+kSAw9RVQTykKzgNAREUQyGFvp1WwC51r1QHMwNyk+wsDG
/h4mbDIgECMeEEGh2qnHgFIVWJ12H5oP/WHVvho/GgVuOkJzCuHTTVYGSaKi43IR
VZqO7784VfzHsHl/caUqv/pOu8MjsynD8QVzac0XrdXHTqYUMWm0rFCrEm+UWFHK
KQK2skzQxFTUTcI2NtG+TjNFiHGs3ZzAfd+N6PuW3FpX3TsNN0fWmFbqgUH0oduV
9Qd2XhZ2TtnAK4+FVCLJDuqk8XkAe9Ibmgelz+aKtwFGN1bx8TilswsvepGjDpMj
AwIDAQAB
-----END PUBLIC KEY-----

Using the site https://jwt.io/, is possible run the signature verify and all it's OK!

Now for better undertand of what is going on I want to gerenate the signature by my self.

My first step was to generate the header using as input this header json:

{"alg":"RS256","kid":"5d340ddbc3c5bacc4cee1fb9d16e9837ec6163eb","typ":"JWT"}

Just translating this with base64UrlEncode and we get:

eyJhbGciOiJSUzI1NiIsImtpZCI6IjVkMzQwZGRiYzNjNWJhY2M0Y2VlMWZiOWQxNmU5ODM3ZWM2MTYzZWIiLCJ0eXAiOiJKV1QifQ

Doing the same transformation on json that represents the payload, and we get the correct result!

{"name":"zagalo","iss":"https://securetoken.google.com/profept-3c479","aud":"profept-3c479","auth_time":1666291403,"user_id":"gRmvqXoKrHO9ODKQDBa0V6tZ50K2","sub":"gRmvqXoKrHO9ODKQDBa0V6tZ50K2","iat":1666291403,"exp":1666295003,"email":"[email protected]","email_verified":false,"firebase":{"identities":{"email":["[email protected]"]},"sign_in_provider":"password"}}

to

eyJuYW1lIjoiemFnYWxvIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3Byb2ZlcHQtM2M0NzkiLCJhdWQiOiJwcm9mZXB0LTNjNDc5IiwiYXV0aF90aW1lIjoxNjY2MjkxNDAzLCJ1c2VyX2lkIjoiZ1JtdnFYb0tySE85T0RLUURCYTBWNnRaNTBLMiIsInN1YiI6ImdSbXZxWG9LckhPOU9ES1FEQmEwVjZ0WjUwSzIiLCJpYXQiOjE2NjYyOTE0MDMsImV4cCI6MTY2NjI5NTAwMywiZW1haWwiOiJyc2p1bGlhb0BnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicnNqdWxpYW9AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0

the last part:

According with jwt.io the signature is:

RSASHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload))

Lets assume that:
H=base64UrlEncode(header)
and
P=base64UrlEncode(payload)
So, SIGNATURE=RSASHA256( H+'.'+P )

How to get the signature? I have been trying all kind of strategy to generate the correct result, but I'm not having success.

What I have to do to get re correct result? My main strategy is to get the result of SHA256 and put this as argument to RSA with my public key above, but the result is not the same. What I'm doing wrong? What I have to learn to solve my lack of knologe?

Upvotes: 3

Views: 9040

Answers (4)

bebbo
bebbo

Reputation: 2959

Here is a java example of how a RS256 signature can be verified:

var bn = ...;                               // BigInteger with your public n
var be = ...;                               // BigInteger with your public e
var base64jwt = ...;                        // your base64 encoded JWT token
var parts = base64jwt.split("\\.");         // the 3 parts
var sContent = parts[0] + "." + parts[1];   // this is hashed
var sha = MessageDigest.getInstance("SHA256");
var hash = sha.digest(data.getBytes());     // the expected hash

Now decrypt the signed hash:

byte[] h = Mime.decode(parts[2].getBytes());// use some base64 decoder
var bh = new BigInteger(1, h);              // create a BigInteger
var bv = bh.modPow(be, bn);                 // apply the public key
var v = bv.toByteArray();                   // the resulting data

For sha256 the last 32 bytes are relevant. You should also check if the data is sound...

var z = new byte[32];
System.arraycopy(v, v.length - 32, z, 0, 32);
var ok = Arrays.equals(hash, z);

So far so good. Using some available library eases this and provides full support to all signature variants. E.g using nimbus-jose-jwt you could use the jwks_uri to retrieve all keys (you get this via the .well-known/openid-configuration of the openid provider).

var parts = base64jwt.split("\\.");         // the 3 parts
var selector = JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL(new URL(jwks_uri));
var sjwt = new SignedJWT(new Base64URL(parts[0]), new Base64URL(parts[1]), new Base64URL(parts[2]));
var processor = new DefaultJWTProcessor<>();
processor.setJWSKeySelector(selector);
var claims = processor.process(sjwt, null);

In case of error you'll get some Exception, in case of success you'll get a claims-object with easy access to all properties.

Upvotes: 1

moldovean
moldovean

Reputation: 3271

so you already did SIGNATURE=RSASHA256( H+'.'+P ) let's call it X1

to verify the JWT, you also need step 2:

RSASHA256(S), where S is the Signature part (after the 3rd .) in the JWT token, call it X2

Based on Math, X1 should equal X2, because S was signed with the private key.

Why? Because: (x^pk mod m)^prk mod m = x where pk and prk is public and private key respectively. it is like going around the clock with 2 different steps (keys) that have the property such that if you do them both (doesn't matter the order) you arrive at the same time (obviously for the clock, Step1 + Step2 = 12h).

Upvotes: 1

Arnaud Grandville
Arnaud Grandville

Reputation: 1039

how to verify a jwt signature with openssl

  1. write the jwt payload (with line breaks for display purposes only) to a file payload.txt
eyJhbGciOiJSUzI1NiIsImtpZCI6IjVkMzQwZGRiYzNjNWJhY2M0Y2VlMWZiOWQxNmU5ODM3ZWM2MTYzZWIiLCJ0eXAi
OiJKV1QifQ.eyJuYW1lIjoiemFnYWxvIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3Byb2Zlc
HQtM2M0NzkiLCJhdWQiOiJwcm9mZXB0LTNjNDc5IiwiYXV0aF90aW1lIjoxNjY2MjkxNDAzLCJ1c2VyX2lkIjoiZ1Jtd
nFYb0tySE85T0RLUURCYTBWNnRaNTBLMiIsInN1YiI6ImdSbXZxWG9LckhPOU9ES1FEQmEwVjZ0WjUwSzIiLCJpYXQiO
jE2NjYyOTE0MDMsImV4cCI6MTY2NjI5NTAwMywiZW1haWwiOiJyc2p1bGlhb0BnbWFpbC5jb20iLCJlbWFpbF92ZXJpZ
mllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicnNqdWxpYW9AZ21haWwuY29tIl19L
CJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0
  1. translate jwt signature from base64_urlencoded to base64 with cyberchef
https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9-_',true,false)To_Base64('A-Za-z0-9%2B/%3D')&input=WmtCcUU4R0NTR3Q5RlhfTHhvYUxOZ0hjUHgxOUVETXEzQVJtWmFKX1IxX0ZpQmNRQXA4VF9BRW1sZVZ1NjhscXc3U2RjTTJhQWpaMWtaYmZrWjQ4aGdmaFcwTEkwM1ZDXzZEYzRzcTlwZ0NIV2FydGVDZVV6NGZFMUI2bmw0bkliS0kzblBRb3JLWVR1ODJTWEV6YVJpRXdIUUNWYXlpTW1ua2p6ajRkLTJZVnA0V0E4SWZfaDNqTkhCZThnaXNrandrQjJ0NmhCMzl2WUxxdmNNNXNFZVNCUnBWVDh6QS1obXAyQWVJbWNYYWdDSzRBdjdKSXRfaUJOdXdUOWR3TUx0QTZhZGRvWGNEWVRKdVJaM0doVnJiTDh4X2lzOXUyWERETFdEV2RyajF5QWprcTdwVFB3QzdLUGZ0OE1kMlBLeHFZUjViaWRfVlJTalBJZWJfazhB

jwt signature base64 encoded (line breaks for clarity) is

ZkBqE8GCSGt9FX/LxoaLNgHcPx19EDMq3ARmZaJ/R1/FiBcQAp8T/AEmleVu68lqw7SdcM2aAjZ1kZbfkZ48hgfhW0LI
03VC/6Dc4sq9pgCHWarteCeUz4fE1B6nl4nIbKI3nPQorKYTu82SXEzaRiEwHQCVayiMmnkjzj4d+2YVp4WA8If/h3jN
HBe8giskjwkB2t6hB39vYLqvcM5sEeSBRpVT8zA+hmp2AeImcXagCK4Av7JIt/iBNuwT9dwMLtA6addoXcDYTJuRZ3Gh
VrbL8x/is9u2XDDLWDWdrj1yAjkq7pTPwC7KPft8Md2PKxqYR5bid/VRSjPIeb/k8A==
  1. write it to a file sha256.sign.pem

  2. convert the base64 signature to a binary file sha256.sign

openssl base64 -d -in sha256.sign.pem -A -out sha256.sign
  1. write to pub.pem file your public key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3CTs4UeO1cS3FJAWDTcO
HHcAeEfZOCFmxju1xC+kSAw9RVQTykKzgNAREUQyGFvp1WwC51r1QHMwNyk+wsDG
/h4mbDIgECMeEEGh2qnHgFIVWJ12H5oP/WHVvho/GgVuOkJzCuHTTVYGSaKi43IR
VZqO7784VfzHsHl/caUqv/pOu8MjsynD8QVzac0XrdXHTqYUMWm0rFCrEm+UWFHK
KQK2skzQxFTUTcI2NtG+TjNFiHGs3ZzAfd+N6PuW3FpX3TsNN0fWmFbqgUH0oduV
9Qd2XhZ2TtnAK4+FVCLJDuqk8XkAe9Ibmgelz+aKtwFGN1bx8TilswsvepGjDpMj
AwIDAQAB
-----END PUBLIC KEY-----
  1. check the signature
openssl dgst -sha256 -verify pub.pem -signature sha256.sign payload.txt
Verified OK

Upvotes: 0

Progman
Progman

Reputation: 19555

It is not possible to generate the signature of a message, when you have only the public key of RSA. You need the private key to generate the signature. The algorithm for the signature using the "RS256" algorithm for JWT is "RSASSA-PKCS1-v1_5 using SHA-256", as defined in RFC 7518 - 3.3. Digital Signature with RSASSA-PKCS1-v1_5:

      +-------------------+---------------------------------+
      | "alg" Param Value | Digital Signature Algorithm     |
      +-------------------+---------------------------------+
      | RS256             | RSASSA-PKCS1-v1_5 using SHA-256 |
      | RS384             | RSASSA-PKCS1-v1_5 using SHA-384 |
      | RS512             | RSASSA-PKCS1-v1_5 using SHA-512 |
      +-------------------+---------------------------------+

The digital signature algorithm "RSASSA-PKCS1-v1_5" itself is defined in RFC 3447 - 8.2. RSASSA-PKCS1-v1_5. The actual signature algorithm is defined in RFC 3447 - 8.2.1 Signature generation operation:

RSASSA-PKCS1-V1_5-SIGN (K, M)

Input:
K        signer's RSA private key
M        message to be signed, an octet string

Output:
S        signature, an octet string of length k, where k is the
         length in octets of the RSA modulus n

As you see, you need the "signer's RSA private key" K to generate the signature for the message M (which would be your JWT header and payload).

You can use the public key only to verify that a given signature is valid, not create new signatures. Only the owner of the private key can do that.

Upvotes: 2

Related Questions