Understanding RSA signing for JWT

2019-03-09 03:35发布

I'm implementing a sign in system with the help of JWT (JsonWebToken) scheme. Basically after a user sign in / login the server sign a JWT and pass it to the client.

The client then return the token with each request and the server verify the token before sending back a response.

This is pretty much how you would expect it but I'm having some problem with the logic of the process. From all the mathematical articles I've read it seems that RSA signing uses asymmetric keys for signing. As the public key, as its name suggests, is exposed to the client and the private key is kept on the server, it make sense to sign the JWT with the public key which is sent to the client and verify it on the server side using the private key.

However on every example and library I see it seems to be the other way around, any idea as to why it's so ? If a JWT is signed with the private key and verified with the public one than whats the point?

3条回答
男人必须洒脱
2楼-- · 2019-03-09 04:28

Your suggestion:

it make sense to sign the JWT with the public key which is sent to the client and verify it on the server side using the private key.

is not correct. Signing is done with the private key of the server, encryption is done with the public key of the client. That is how PKI works in general.

查看更多
看我几分像从前
3楼-- · 2019-03-09 04:38

There is a difference between signing/verifying and encrypting/decrypting data but the semantics can be similar.

You sign data with a private key that only controlled sources have so anyone who receives the information can use your public key to validate this information was indeed sent by you and is the same information you intended to send out.

You encrypt data with a public key and decrypt with a private key. This sounds opposite but really follows the same logical concept as signing. If you want to send data between person A and person B, both people have a public/private key pair and they share their public keys with each other when they meet (handshake). A constructs a message for B and encrypts it using B's public key and sends it to B. Now, no one without B's private key can decrypt that message including A - even though they originally sent it.

In terms of JWT, a JWT payload by itself is just Base64 encoded JSON with some standardized fields. The signature allows someone with the public key to validate the information hasn't been altered by someone in the middle. Similar to a checksum but with some extra security based warm fuzzy feelings. The contents of the signed JWT are easily visible (base64 is encoding like unicode or utf-8, not encryption) to the end user and anyone in the middle which is why it is generally frowned upon to send sensitive data in a JWT like passwords or PII.

As others have mentioned, most JWTs contain information not intended for clients but to help facilitate the stateless part of RESTful services. Commonly, a JWT will contain an accountid, userid and often permissions as "claims". An API endpoint can verify the signature and reasonably trust the claims to not be altered by the client. Having the client send the JWT for each request saves the endpoint having to do a lot of database back and forth just to get where they are by simply verifying a signature with a public key.

Additionally, signed JWTs can be encrypted. According to the JWE spec, the payload is encrypted after signing and then decrypted before verifying. The trade off here is that all endpoints must also have the private key to decrypt the JWT but end users won't be able to see the contents of the JWT. I say trade off because in general private keys are meant to be kept secure and a widely distributed private key is just less secure. Security, risk assessment and cost/benefit of encryption is a whole other beast :)

查看更多
劫难
4楼-- · 2019-03-09 04:42

First off, apologies, this answer got rather long.

If you use RSA to encrypt your tokens, and a connecting client is a web browser, the client will never see the RSA keys (public or private). This is because the client presumably doesn't need to verify that the JWT is valid, only the server needs to do that. The client just holds onto the JWT and shows it to the server when asked. Then the server checks to make sure its valid when it see's the token.

So why might you need a public / private key combo for JWT's? Well first off, you don't need to use a public / private key algorithm.

You can encode JWT's with a number of different algorithms, RSA being one of them. Other popular choices for encoding your JWT's are ECDSA or HMAC algorithms (the JWT standard supports others as well). HMAC, specifically, is not a public / private key scheme. There's just one key, the key, which is used to both encrypt and decrypt the tokens. You can think of this as using the private key for both signing and decrypting the JWT's. I'm not an expert on this by any means, but here's the conclusions I came to from doing my own research recently:

Using HMAC is nice because it's the fastest option. However, in order to decrypt the JWT's, you need to give someone the one key that does everything, Sharing this key with someone else means that that person could now also sign tokens and pretend like they're you. If you're building multiple server applications that all need to be able to verify your JWT's, you might not want every application to have the ability to encode tokens as well (different programmers might be maintaining the different applications, sharing the encypting ability with more people is a security risk, etc). In this case, it's better to have one, tightly controlled private key (and one app that does the signing) and then share the public key around with other people to give them the ability to verify the tokens. Here, the private key is used for encrypting the tokens, and the public key is used for decrypting them. In this case you'd want to choose RSA or ECDSA.

As an example, you might have an ecosystem of apps that all connect to the same database. To log users in, each app sends folks to one, dedicated, 'logging in' app. This app has the private key. The other apps can verify that the person is logged in using the public key (but they can't log people in).

The research I've done points to RSA being the better option for most JWT apps in this scenerio. This is because your app will be, theoretically, verifying tokens frequently. RSA is much faster then ECDSA at verification. ECDSA is primarily nice because the keys are smaller in size. This makes it better for https certificates because you need to send the public key to the client's browser. In the JWT scenerio though, the keys are staying on a server so the storage size is n/a and the verification speed is more important.

Conclusion: if you're building a small app without multiple smaller 'micro-service apps' / you're the only developer, probably choose HMAC to encrypt your keys. Otherwise, probably choose RSA. Again though, I'm not an expert, just someone who recently googled this topic, so take this with a grain of salt.

查看更多
登录 后发表回答