I have implemented an authentication scheme using JWT with assymetric keys (RS256). The idea is that (assuming some microservice-based acrhitecture) the authentication service will sign all JWTs with a secret/private key (PRIVATE_KEY) and distribute the public key (PUBLIC_KEY) to all other microservices, so they can verify whether 1) the JWT was issued by the authentication service and 2) the JWT was not tampered in any way.
I have generated a private and public key pair with:
$ ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
$ openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
I then created a signed JWT using python-jose
(RS256 with PRIVATE_KEY). Below is the JWT and the key pair.
JWT:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YjM5ZDQ3Mi1iNmU4LTQwOTktOWNkYi1hMWVmYTMzMWEwM2UiLCJpYXQiOjE2NjY1MjA3NjF9.DbQRuAvZdO7rJGyWRHBtdpeiDaN2Dc3LFi2aiCX3Z6YXpGBoLVyOofO05SSPerdgTm_rKrzvtXGlBg3ZojjdpUPqR0SdPjUhYnfvViIpZWWCC34H_MI99jacH8LtINL-BCJ-t-xdwojhS35uljxiHLmnN2ekdbVFbfULS5f-F2a7xyD7axZXjziwxOJUQC9cp71qc6Va5G7wWIAca1Xq6x2Gq5bsUeQ2QjoLLUmrPZ7KRPklGgjZcPkNqDLk7fe6bgfVWQkpapMuIr6zDv6yN90M2SzKHIhCvsUa4s01dOIx10v39NpHd32-buiGcDgLRMM98OM6us6MGSncrIo3ngj4TrDlK7URHapHnKPdmb_x1If7yjMwQyC8it72QSQD1m0BvNoULHILaZSuStiZLWsosq8tZB9KTR-PpnIW32jLKqzTzuPGcH7hnBjBW9YMhxa42RkNDHh7NUa2FRvQ3oFv7auZKer3BaAW1ExzQfKJ-BKOr93hWCw9W76ulOmpzqreaUUhp7nUdcCItV-OWQJnp1nwgiej59ZgjKsxURbjdZ2UtQzPOn8ra506TQs3FcBZDAqO-bIA7TCQvSu0I_rJLM1nE85znSB_UQZZQVP0-52vxSs30Oc8l6mkTFxgRTSMREgOIsjWY_OXVQNEHjCByzaGcTiUIacxxHMjxTM
PRIVATE_KEY:
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
PUBLIC_KEY:
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA8XrqQgXpK1JA4iXzpx8p
nmnV2KvFHZ5Jsz48Fy8Z923VvloSsNF0KDS6kmOIfpx8jnXeAOmt6ctW0KM5WEFu
8fvgBvyB1Jvqgnr7kLDd+rhgoKwt5eCFAtfchNxq8HKle5P/4d2k/Kf7Swn89IBR
pDZUiL3GHivyEIlXcATg0j8P5ekvJKMcLZqdak+2fxQkNLGXLXSydFS7capio/7a
erj+U3XBvz2lknWdaKADOjPju06vgwP8/1Ru7aqxFVoQABA1as2Ue5v8Vo+wKkJq
bB3HSaWn1bWhsFYSEjrVmnGXUVWzwQHckMYCdxwPofy7ufB/7Rg8PfGg/j3HEolN
8BOL8PgnZIkDRV5f+RFfupMGn1T9JEkDDAxuE2+Ac7v5ZFXZH0IoWPzwtpS49O+K
dzkkGtTd75q6+3XCvX8lViYDOQvc6Ik3zN9TYTtUWRNGCRdNkBZVxM3W6Mz7TpAI
Y5YEu7xC7T6kZgmQfmFwnF/an/6q2Gy30eZzLo2iLSUYU0t/hVWCHFBSlly/RApm
RBuppC2liIYeXbmuDA46ts1Ra9+N/D0smPHfoqcutX42W8PlYNaMoQAd4/QS96KQ
ZNXUoJVsb7OgRi0OFqWijPer7nm3sNfsu/JB2GTRNI9/S3EjbA6lsGOjqxYWMyUE
zPRjDUoziVxH2lDGWqSBhM8CAwEAAQ==
-----END PUBLIC KEY-----
To test my implementation I entered the JWT on jwt.io
and confirmed that the signature was valid. I then perturbed the JWT by changing the last character from an "M" to an "N" and to my surprise the signature was still valid (given PUBLIC_KEY). Now finally I get to my questions:
Perturbed JWT:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YjM5ZDQ3Mi1iNmU4LTQwOTktOWNkYi1hMWVmYTMzMWEwM2UiLCJpYXQiOjE2NjY1MjA3NjF9.DbQRuAvZdO7rJGyWRHBtdpeiDaN2Dc3LFi2aiCX3Z6YXpGBoLVyOofO05SSPerdgTm_rKrzvtXGlBg3ZojjdpUPqR0SdPjUhYnfvViIpZWWCC34H_MI99jacH8LtINL-BCJ-t-xdwojhS35uljxiHLmnN2ekdbVFbfULS5f-F2a7xyD7axZXjziwxOJUQC9cp71qc6Va5G7wWIAca1Xq6x2Gq5bsUeQ2QjoLLUmrPZ7KRPklGgjZcPkNqDLk7fe6bgfVWQkpapMuIr6zDv6yN90M2SzKHIhCvsUa4s01dOIx10v39NpHd32-buiGcDgLRMM98OM6us6MGSncrIo3ngj4TrDlK7URHapHnKPdmb_x1If7yjMwQyC8it72QSQD1m0BvNoULHILaZSuStiZLWsosq8tZB9KTR-PpnIW32jLKqzTzuPGcH7hnBjBW9YMhxa42RkNDHh7NUa2FRvQ3oFv7auZKer3BaAW1ExzQfKJ-BKOr93hWCw9W76ulOmpzqreaUUhp7nUdcCItV-OWQJnp1nwgiej59ZgjKsxURbjdZ2UtQzPOn8ra506TQs3FcBZDAqO-bIA7TCQvSu0I_rJLM1nE85znSB_UQZZQVP0-52vxSs30Oc8l6mkTFxgRTSMREgOIsjWY_OXVQNEHjCByzaGcTiUIacxxHMjxTN
- Is this behaviour expected? As in, should it be this easy to find other valid signatures?
- Is it possible that the signature will remain valid even after the payload is changed? I.e. in this example I changed a character from the signature, but I may as well have changed a character in the payload.
- Is this approach valid and secure?
(P.S. I have implemented all of this to teach myself the basics of authentication and this approach is not meant to be deployed to a production system.)