Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js: Signature verification (PS256) succeeds in Node.js but fails in jwt.io debugger

I wrote a test script with which I'm signing and then verifying a JWT with the PS256 algorithm.

My code verifies the JWT successfully, but the verification fails in the jwt.io debugger.

I'm using [email protected].

This happens only when using the PS256 algorithm, not when using, for instance, RS256.

Am I doing anything wrong?

I generated my keys with:

openssl genpkey -algorithm RSA -out private_key2.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key2.pem -out public_key2.pem

You can try my code on repl.it: https://repl.it/@SamArtuso/Nodejs-Signature-verification-PS256-succeeds-in-Nodejs

Code:

const { join } = require('path');
const { readFileSync } = require('fs');
const jws = require('jws');

const ALG = 'PS256';

/**********
 * SIGNING
 **********/

const PRIVATE_KEY_PATH = join(__dirname, './keys/private_key2.pem');

const privateKey = readFileSync(PRIVATE_KEY_PATH).toString();

const payload = {
  foo: 'bar',
};

const token = jws.sign({
  header: { alg: ALG },
  payload,
  privateKey,
})

console.log('Token:');
console.log(token);

/************
 * VERIFYING
 ************/

const PUBLIC_KEY_PATH = join(__dirname, './keys/public_key2.pem');

const publicKey = readFileSync(PUBLIC_KEY_PATH).toString();

const result = jws.verify(token, ALG, publicKey);
if (result) {
  console.log('Verification successful.');
} else {
  console.error('Verification failed.');
}

Private key:

-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPR8CD/9V9fddR
vMk590JtuU4hPT9iPD/mYeWdvUTbkN2iPpg6LDADBntz8I9CzThCkgHmDQhh49Qz
AONiHfyHhKT6HHIBS78wGWfhE9ueFtkv19xisvFJTDO6IllGFMiioj8AkvOJwaY+
5ZKSFG32V0gMaglNSNTOh5KK6DsxgdH2KfMzn6uFwJkvsz1qwPiINu/rqvOQPOIK
JCbNHBOg/SvhPWBSEFDC9AkLm3ajhGAcnSlWi0KgUFz7iHuUR6s4GLdkc40uAooY
ExEWsZyoByT18tXln2hPAvE1Ata2PSbtLHuwvMQt4vzZi80K4BXqOiqvNMo1A1aT
oSQnmOwtAgMBAAECggEAPpHTPEVS5aHCCItrVtMbu1FvkzsQ0g+L3nh4vqfujDTr
olkwzIagK5meVH4uUKTwMbAvYIlYmWwTlx3ShcC1hRb2UgWaKGf8G4HfyKKc7djJ
0NZhUW3gxhZ5mttZhX0qn2VIjVzOpSvOijf0iaIfG3h3aD/t9OViT8G+6610iNmz
Oygmi9kQaBAS2A2sLSSMhdyddIPEpZ8QOHwGCRvBXWO100BCV9ROQzYxW+U9VPaJ
I0tXDL1L1H7y1EbxccPfOsfUPMtF9LQWQZ6ksJuSRwkBAnfKpzexguph29sLDjCQ
X23rQN/NwiU+zRmn+cW7VqkZsbSqucP9t+d19UdWpQKBgQDn9sThxAZvx4OS0noD
AWJX5CliDNlagmLqr4c0QoYI+fPL7FcK9tQYUZ869jnebX7UPHTuDhMoIvWknZbB
QhoogpRoX+XFtxzPwjtBBGc5TYacYnOR4xPSjvDlIrY7FQiH8TgpAGzenSBiD8O0
xW4zSaHLUDHEMFEyPq8tdMmBewKBgQDkwjUnVMPlA9AOD+KQNXgf2kV9z0ZA5idt
9WcsXsxpO2W6S0ntkr29Yr3VuuuJTRH7Fm+bPDJ6C1bfZ746hn3iLMZbMxgGVEf/
cN0nNXjfG5G7EEIU9WFges1I5rec/5W38kIco6soP36Qj5HcUBXt6AHy3k1IbEL2
84Bp1SZ0dwKBgF19mhCcXzPCKAePCVoYvrhJ31wDbb8K+i84m7e2cCtCAr7X/KUQ
op9Clni/MMezPgDwdPhVd+cfX/3+/fnaWIynRIVk0UkE6nnaAOPNkIUJ+A0jqQzN
hvnAXtsbSHM7oPqZgFcWMsrubVTYobpEMIw/SxSUt9oo1zD3Dse1YFntAoGBAIAL
AIuKU7f9gVhNpehINXvGxfzcpxsueEWBBgX87fe9CnzTJYc4CsJV4aIfZTVOEVF0
xnWipTJQ5IhZ3k2N+CpAG6ryl3D7fe1J9E+9C3H+UXzeZc6rZp0FP6Cdm4riOKBk
loLHTcdSevpZkjA6F3w5z9Vsft+Z0YW+2FLkvwiTAoGAdjUYcGTaEhGNtHQszAeY
2VbeVHwgnajKhaZpKMNTVp/AFgeS5k1lSATlT5wQHD3OAhUn+NAEwUOUBBWOpg+i
jML8loplEcVmdHkfGFsrIvzFFIIXQOWy77s/McTCh2jLkLYO8kIOrGy08JBiZnQ/
Ki/HFS/2yaUbG6UdH8FFp2g=
-----END PRIVATE KEY-----

Public key:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz0fAg//VfX3XUbzJOfdC
bblOIT0/Yjw/5mHlnb1E25Ddoj6YOiwwAwZ7c/CPQs04QpIB5g0IYePUMwDjYh38
h4Sk+hxyAUu/MBln4RPbnhbZL9fcYrLxSUwzuiJZRhTIoqI/AJLzicGmPuWSkhRt
9ldIDGoJTUjUzoeSiug7MYHR9inzM5+rhcCZL7M9asD4iDbv66rzkDziCiQmzRwT
oP0r4T1gUhBQwvQJC5t2o4RgHJ0pVotCoFBc+4h7lEerOBi3ZHONLgKKGBMRFrGc
qAck9fLV5Z9oTwLxNQLWtj0m7Sx7sLzELeL82YvNCuAV6joqrzTKNQNWk6EkJ5js
LQIDAQAB
-----END PUBLIC KEY-----

Edit: Clarifications on how to reproduce my issue.

  1. Run my script with: npm init -f && npm install jws && node test.js
  2. The script will output the token and Verification successful.
  3. Copy and paste the token in the "Encoded" field on jwt.io
  4. Copy and paste the public key in the "Public key or certificate" field.
  5. A red "Invalid Signature" message appears at the bottom.

The below is an example of the token my script will generate. Bear in mind that PS256 has a probabilistic rather than deterministic signature, so the signature will be different every time you run the script.

eyJhbGciOiJQUzI1NiJ9.eyJmb28iOiJiYXIifQ.Rn6i-94ovuKOVRc2jzLVS40MpMmBkIfkyvF56JK3tM8wvg-DW943NNbXf01bhRdyoUj0A73NSQtz0kB4WfXN1uAH1omzNr0ww-iTfC23AX0OcjbsE7CcDz_ZQWWOzwEGGFVfV9ez5yn1pKRYVdFaKqApk3irP-ej_WGrrJfgeZVs683lNk0WjKOYhj6vsryuz52c0OEJ0UmYy7hSSfL38jgL6bdE0awg2DgiaU6qszZEkMjSnugoMobeAUUyOiXHsR79NryuhCQko-I9P1vKd1dEA3zM1iut5sW5FwB8K9Fi49gaQy0zHk72txJvUWdxW5ns0Svft3qLke5XMuqm2g
like image 352
kYuZz Avatar asked Oct 20 '25 02:10

kYuZz


2 Answers

I tried to copy and paste your token, your public key and your private key to the debugger of jwt.io. Here are my findings:

  1. If you only paste the token, the token is decoded correctly, but the signature can't be verified (which is obvious).
  2. If you then paste the public key (which should be enough to verify the signature), the signature still can't be verified. This is strange 🤔
  3. If you then paste the private key as well (which should not be required to verify the signature), the signature is shown to be valid.

So, apparently things come down to the question why the token's signature can be verified only if the private key is given…

This led me to the assumption that somehow the private key and the public key don't match each other. So I tried to regenerate (or better, extract) the public key from the private key you have given and compare that one to the public key you suggested. So, I pasted your private key into a file named privateKey.pem, and ran the following command:

$ openssl rsa -in privateKey.pem -pubout

This gave me the following public key:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz0fAg//VfX3XUbzJOfdC
bblOIT0/Yjw/5mHlnb1E25Ddoj6YOiwwAwZ7c/CPQs04QpIB5g0IYePUMwDjYh38
h4Sk+hxyAUu/MBln4RPbnhbZL9fcYrLxSUwzuiJZRhTIoqI/AJLzicGmPuWSkhRt
9ldIDGoJTUjUzoeSiug7MYHR9inzM5+rhcCZL7M9asD4iDbv66rzkDziCiQmzRwT
oP0r4T1gUhBQwvQJC5t2o4RgHJ0pVotCoFBc+4h7lEerOBi3ZHONLgKKGBMRFrGc
qAck9fLV5Z9oTwLxNQLWtj0m7Sx7sLzELeL82YvNCuAV6joqrzTKNQNWk6EkJ5js
LQIDAQAB
-----END PUBLIC KEY-----

Unfortunately, this is the very same public key that you provided, too, so we can tick this thought off from our list.

So, I started to search the web and found this blog post by Auth0 where they describe the introduction of support for PS* algorithms, but according to the documentation, everything should work as expected. Hmmm, more strange 🤔

So, the next thing I tried was to encrypt things using jwt.io's debugger, using the decoded values of your token, with algorithm PS256 and your provided private key. This brought me to this token:

eyJhbGciOiJQUzI1NiJ9.eyJmb28iOiJiYXIifQ.wzkD7q5lH_qJw_V6DJLk72TDqpzA05KN3XHfEFOeAQgCriodoOLrgQ-p3ifIzK525puiV1hnXQuJ-6TEZRqiO8dqlPWQJbG1mLdTTx7ZwdUKsDjY6CMsmsskU8eVrzfaSDVmGvbjaWKq1_KGvtk4vMT0Z9m_YhUjD_SDVbCaZReEbzxta4APM7dMQ5mRFzD03JTwe05_AqayrfXdTungeBEJcMB0tY_4FjWiKbBZOpyQLebzPJ69VgpQFGvf6fPuKT6_3LTEHPrRNFL05OYpgRvayh3pEAF297aSCQ_SFi9slqDdVKkor-q8UDO-zM7J952uCkr7NkXV0Loq62_gMg

If you paste this token into jwt.io's debugger, and use your public key, then the signature is valid. So, basically PS256 seems to work with jwt.io, but for whatever reason not for your token…

This finally leads me to the thought that maybe Node.js and jwt.io do use different ways to run the PS256 algorithm. Of course, since it's a standard, both should work the same way, but apparently they don't. I don't know who of both does it wrong, or if it's fine that both work in different ways, as I never had to deal with PS256 before.

I'm very sorry that I have no better answer for you here, but I guess that the only way to find out the real reason is to debug into Node.js's crypto code, and to have a look at jwt.io's source code (which is not publicly available, as far as I know).

From my point of view, what you do is correct, and I'd rather guess that this is a bug in either Node.js or jwt.io, or in both 😉

PS: After having written this answer, I found this issue, and I guess the original author is you (aren't you?), and the reply there basically comes to the same conclusion, that it's a problem of the website. So maybe this actually is the best answer you can get.

like image 96
Golo Roden Avatar answered Oct 21 '25 17:10

Golo Roden


I faced similar problem using openssl API in my c++ app. Apparently my app could produce PS256 signature and pass its validation by itself, but on jwt.io validator signature validation failed. So after some research I discovered that openssl on my platform automatically set wrong salt length for PS256 signature algorithm. For PS256 it should be 32. When I set salt length to 32 manually - signatures produced by app passed validation on jwt.io.

P.S.: When You post private key in jwt.io (PS256 alg) payload is automatically re-signed, and if You have set right public key - signature is verified as valid. JWT io can validate signature basing on public key only(when You have right signature for the payload).

like image 38
jurcix Avatar answered Oct 21 '25 16:10

jurcix



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!