Reputation: 133
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!
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
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
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
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
Reputation: 1039
payload.txt
eyJhbGciOiJSUzI1NiIsImtpZCI6IjVkMzQwZGRiYzNjNWJhY2M0Y2VlMWZiOWQxNmU5ODM3ZWM2MTYzZWIiLCJ0eXAi
OiJKV1QifQ.eyJuYW1lIjoiemFnYWxvIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3Byb2Zlc
HQtM2M0NzkiLCJhdWQiOiJwcm9mZXB0LTNjNDc5IiwiYXV0aF90aW1lIjoxNjY2MjkxNDAzLCJ1c2VyX2lkIjoiZ1Jtd
nFYb0tySE85T0RLUURCYTBWNnRaNTBLMiIsInN1YiI6ImdSbXZxWG9LckhPOU9ES1FEQmEwVjZ0WjUwSzIiLCJpYXQiO
jE2NjYyOTE0MDMsImV4cCI6MTY2NjI5NTAwMywiZW1haWwiOiJyc2p1bGlhb0BnbWFpbC5jb20iLCJlbWFpbF92ZXJpZ
mllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicnNqdWxpYW9AZ21haWwuY29tIl19L
CJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0
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==
write it to a file sha256.sign.pem
convert the base64 signature to a binary file sha256.sign
openssl base64 -d -in sha256.sign.pem -A -out sha256.sign
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-----
openssl dgst -sha256 -verify pub.pem -signature sha256.sign payload.txt
Verified OK
Upvotes: 0
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