Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"JsonWebTokenError: invalid signature" when verifying JWT signed with Java JWT #208

Closed
nodje opened this issue May 31, 2016 · 28 comments
Closed

Comments

@nodje
Copy link

nodje commented May 31, 2016

I use https://github.com/jwtk/jjwt to encode and sign a token as follow:

    Jwts.builder()
        .setSubject(authentication.getName())
        .claim(AUTHORITIES_KEY, authorities)
        .signWith(SignatureAlgorithm.HS512, "my-secret-token-to-change-in-production")
        .setExpiration(validity)
        .compact();

then decode in node.js as follow:

var decoded = jwt.verify(req.get('Authorization'), 'my-secret-token-to-change-in-production', { algorithms: ['HS512'] });

and get the error:

JsonWebTokenError: invalid signature

Using jwt.decode I get the token content without problem.

Am I doing something wrong?

@omsmith
Copy link

omsmith commented Jun 5, 2016

I'd love to give you a hand with this. Would you be able to provide an example token and the secret you used to sign it so I can take a look. With what you've provided, hard to say - from looking at jjwt your example should be throwing since "my-secret-token-to-change-in-production" is not base64.

@nodje
Copy link
Author

nodje commented Jun 6, 2016

hum, the base64 issue sounds like a good lead, but I couldn't verify the signature with the secret encoded in base64 like this:
var decoded = jwt.verify(req.get('Authorization'), new Buffer('my-secret-token-to-change-in-production').toString('base64'), { algorithms: ['HS512'] });
Here's a token generated by jjwt with my favorite secret my-secret-token-to-change-in-production:
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImF1dGgiOiJST0xFX0FETUlOLFJPTEVfVVNFUiIsImV4cCI6MTQ2Nzc3MzQ5Nn0.v0Gfc9fKDQfkDningjmObkD5-EcbfWy5vuvuOimTV032iCoOaaQtCsZxQC78JbLbeQNLUA3UaQnuLgvwwqLmIg

@DaleWebb
Copy link

DaleWebb commented Jul 5, 2016

I'm having the same problem with these libraries.

I used the debugger at jwt.io to decode it, which gave the option to provide the secret as base64 - which worked on the debugger, but not in my program.

However, I couldn't get the debugger to work with your values, @nodje

@michaelcbarr
Copy link

Did anyone get a solution to this yet? I am signing the JWT in Java (io.jsonwebtoken) and trying to "unsign" using this npm library in a separate node.js app. As already mentioned by others, the token decodes fine, so obviously the data has not been corrupted - it just seems that the sign/unsign procedures do not match up.

@omsmith
Copy link

omsmith commented Jul 6, 2016

@DaleWebb @michaelcbarr would either of you be able to provide some actual code you're trying to do this with?

@michaelcbarr
Copy link

michaelcbarr commented Jul 6, 2016

no problem - this was borrowed form a tutorial - can't find the link but will credit if I find it :D

var jwt = require('jsonwebtoken');
jwt.verify(token, superSecret, function(err, decoded) {
                if (err) {
                    return res.json({ success: false, message: 'Failed to authenticate token.' });
                } else {
                    // if everything is good, save to request for use in other routes
                    req.decoded = decoded;
                    next();
                }
            });

The token is created in Java with code similar to this:

private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(this.generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
   }

@omsmith
Copy link

omsmith commented Jul 6, 2016

@michaelcbarr Might you be able to provide the java code where you're signing?

@michaelcbarr
Copy link

added above!

@michaelcbarr
Copy link

NB: I have tried SignatureAlgorithm.HS256 but makes no difference

@DaleWebb
Copy link

DaleWebb commented Jul 6, 2016

@michaelcbarr No, I haven't found a solution to this

@michaelcbarr
Copy link

@omsmith Can you send me a test token which works for you? I tried the one above with that secret but no luck!

Me neither @DaleWebb - have you tried any other workarounds? I had thought about manually encrypting the token and use the decode function rather than verify but it seems hacky :(

@DaleWebb
Copy link

DaleWebb commented Jul 6, 2016

private static String generateToken(Account account, App app) {
        return Jwts.builder()
            .setSubject(account.getId())
            .claim("app_id", app.getSlug())
            .setExpiration(new Date(new Date().getTime() + TOKEN_LIFETIME))
            .signWith(SignatureAlgorithm.HS512, PersonaApplication.getSecret())//the secret is a String, not base64 encoded.
            .compact();
    }

@DaleWebb
Copy link

DaleWebb commented Jul 6, 2016

@michaelcbarr If I cannot get a solution before we have to ship the feature, I'm going to create a route on the gateway that creates the token to give the information back.

@omsmith
Copy link

omsmith commented Jul 6, 2016

@nodje @DaleWebb @michaelcbarr

I'm not going to take the time to dig into exactly what the java library is doing by default (I don't work with Java usually, needed to install Eclipse and figure and Maven and things :)). However, the issue is the string secret. By calling getBytes("UTF-8") on your secret and providing signWith() with the byte[], everything will work out.

package jwt_test.foo;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.io.UnsupportedEncodingException;
import java.util.Date;

/**
 * Hello world!
 *
 */
public class App 
{
    private static String SECRET = "HelloWorld!";

    public static void main( String[] args ) throws UnsupportedEncodingException
    {
        System.out.println( makeToken() );
    }

    private static String makeToken() throws UnsupportedEncodingException {
        return Jwts.builder()
                .setSubject("foo")
                .claim("bar", "baz")
                .signWith(SignatureAlgorithm.HS512, SECRET.getBytes("UTF-8"))
                .compact();
    }
}
'use strict';

const JWT = require('jsonwebtoken');

const SECRET = 'HelloWorld!';
const JAVA_JWT = 'eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmb28iLCJiYXIiOiJiYXoifQ.1MOXGiwGTFLU7-YMvOe2_q2ZRUHAMCVS7pbnOkRKCFV1HIvY8odBaqWVCQRuT2RUbKtGgA2elFRsuka4K1KP7A';

JWT.verify(JAVA_JWT, SECRET, { algorithms: ['HS512'] })

@michaelcbarr
Copy link

michaelcbarr commented Jul 7, 2016

@DaleWebb @omsmith

AWESOME WORK!

I can confirm that this works perfectly for me - just need to remember to convert the SECRET string to bytes on Java verify method.

claims = Jwts.parser()
                    .setSigningKey(secret.getBytes("UTF-8"))
                    .parseClaimsJws(token)
                    .getBody();

(Also need to catch the UnsupportedEncodingException!)

Think I would still be scratching my head next month without your help - thanks again @omsmith !

Mb

@DaleWebb
Copy link

DaleWebb commented Jul 7, 2016

Great! I can confirm that this solution works too.

@lhazlewood
Copy link

An author of JJWT here...

FWIW, cryptographic signatures are always computed with byte array keys - never strings. You can get the UTF-8 bytes of a String as demonstrated above, but that only masks what could be a very problematic cryptographic weakness (I'm not saying those in this thread are experiencing that weakness - I'm just raising that this could happen to anyone that might not understand what is going on).

Digital signature keys (again, byte arrays), should ideally never be based on simple strings like 'my secret' or 'my password'. Or, at the very least, if a simple password should be used as a signing key, it is almost always better to send it through a key-derivation algorithm (like PBKDF2) and then use that resulting output as the signature key. This ensures sufficient cryptographic entropy (randomness) that short, human-readable strings don't (and which are therefore risky).

Signing keys should ideally always be:

  1. generated by a secure-random number generator or at the least created via a cryptographically secure key-derivation function and
  2. and this is important - of sufficient length for the hashing algorithm to be used..

Number 2 is why JJWT provides the MacProvider.generateKey method - to ensure you always have keys of sufficient strength for the algorithm chosen. You can then easily base64 the result:

SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256);
String base64Encoded = TextCodec.BASE64.encode(key.getEncoded());

This is why JJWT expects Base64 by default - because if you do these best practices, you'll always end up with a byte array key (e.g. key.getEncoded()). And if you have a byte array key, the most common way to turn that into a string (e.g. for configuration) is to Base64-encode that byte array.

Finally, note that TextCodec.BASE64.decode(myKey) does NOT produce the same byte array (key) as myKey.getBytes('UTF-8'). The latter is usually incorrect in cryptographic contexts.

That means that my-secret-token-to-change-in-production.getBytes("UTF-8") might represent a weakened signing key, and as a result, shouldn't be used. I recommend dumping that current key and generating a new one with strong cryptographic guarantees as shown above (e.g. using JJWT) and ensuring your Node library base64-decodes your string correctly.

HTH!

@mgkeen
Copy link

mgkeen commented Nov 3, 2016

I'm having a similar issue. I'm generating a key elswhere, and trying to validate it using this library. It validates fine in other services using libraries from different languages.
Looking at this issue and the answers to it, am i right in assuming that the node library generates a byte array for validating by UTF-8 decoding the supplied secret string? Is there any way to get it to base64url-decode the key (as i believe would be the "proper" way for the reasons described by the previous comment)? I've tried having a dig through the code but haven't come up with anything.

EDIT: Just figured it out. Passing new Buffer(secret, 'base64') instead of the string in did the trick!

@toymachiner62
Copy link

The solution @mgkeen provided in his edit worked for me.

If this is the appropriate thing to do can someone update the documentation? i.e.

var tokenDecoded = jwt.verify(token, new Buffer(MYSECRET, 'base64'), function(err, decoded) {

@DmanDman
Copy link

Still getting this error when trying the following:

var user = { username: 'batman' };

// Signing the token
var token = jwt.sign( user, 'ThisStringIsASecret' );

// Generated token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJhdG1hbiIsImlhdCI6MTQ4NTM3MTc0MywiZXhwIjoxNDg1Mzc1NzQzfQ.nxFDJGNnEvx4wDwcPk0puvcvtJArm2MxpZc4bn0XrSs

// Verifying the token
jwt.verify( token, new Buffer( 'ThisStringIsASecret', 'base64' ), function ( err, decoded ) { /**/ });

@golinmarq
Copy link

@DmanDman are you signing your token just like that? You should use new Buffer( 'ThisStringIsASecret', 'base64' ) to sign your token

@ralphgabrielle
Copy link

@nodje @DaleWebb @michaelcbarr
I'm not going to take the time to dig into exactly what the java library is doing by default (I don't work with Java usually, needed to install Eclipse and figure and Maven and things :)). However, the issue is the string secret. By calling getBytes("UTF-8") on your secret and providing signWith() with the byte[], everything will work out.
package jwt_test.foo;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.io.UnsupportedEncodingException;
import java.util.Date;

/**

  • Hello world!

*/
public class App
{
private static String SECRET = "HelloWorld!";

public static void main( String[] args ) throws UnsupportedEncodingException
{
    System.out.println( makeToken() );
}

private static String makeToken() throws UnsupportedEncodingException {
    return Jwts.builder()
            .setSubject("foo")
            .claim("bar", "baz")
            .signWith(SignatureAlgorithm.HS512, SECRET.getBytes("UTF-8"))
            .compact();
}

}
'use strict';

const JWT = require('jsonwebtoken');

const SECRET = 'HelloWorld!';
const JAVA_JWT = 'eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmb28iLCJiYXIiOiJiYXoifQ.1MOXGiwGTFLU7-YMvOe2_q2ZRUHAMCVS7pbnOkRKCFV1HIvY8odBaqWVCQRuT2RUbKtGgA2elFRsuka4K1KP7A';

JWT.verify(JAVA_JWT, SECRET, { algorithms: ['HS512'] })

This is deprecated, is there's an update here? Thanks

@lhazlewood
Copy link

@ralphgabrielle assuming the latest stable JJWT version, which is 0.10.7 at the time of writing, you can do:

public class App {

    // Note that 'HelloWorld' is not a valid JWT signing key per the JWA RFC's 
    // key strength requirements.  For why, read this for more information:
    // https://stackoverflow.com/a/40274325/407170
    // Change this to something else or derive a key using PBKDF2
    private static String SECRET = "HelloWorld!";

    public static void main( String[] args ) throws UnsupportedEncodingException {
        System.out.println( makeToken() );
    }

    private static String makeToken() throws UnsupportedEncodingException {
        Key key = Keys.secretKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
        return Jwts.builder()
                .setSubject("foo")
                .claim("bar", "baz")
                .signWith(key)
                .compact();
    }
}

@ralphgabrielle
Copy link

@ralphgabrielle assuming the latest stable JJWT version, which is 0.10.7 at the time of writing, you can do:
public class App {

// Note that 'HelloWorld' is not a valid JWT signing key per the JWA RFC's 
// key strength requirements.  For why, read this for more information:
// https://stackoverflow.com/a/40274325/407170
// Change this to something else or derive a key using PBKDF2
private static String SECRET = "HelloWorld!";

public static void main( String[] args ) throws UnsupportedEncodingException {
    System.out.println( makeToken() );
}

private static String makeToken() throws UnsupportedEncodingException {
    Key key = Keys.secretKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
    return Jwts.builder()
            .setSubject("foo")
            .claim("bar", "baz")
            .signWith(key)
            .compact();
}

}

Thanks, big help. That worked.

lcc3108 added a commit to lcc3108/jclip-mailserver that referenced this issue Dec 12, 2019
auth0/node-jsonwebtoken#208 (comment)
자바와 nodejs의 jwt토큰 호환성 에러해결
lcc3108 added a commit to lcc3108/jclip-mailserver that referenced this issue Dec 14, 2019
* .

* update aws context

* update .env

* encrypt .env && google_key

* add enviroment tar file

* update apollo server context

base64 encoding update

* .

* .

* .

* jwt verify update

auth0/node-jsonwebtoken#208 (comment)
자바와 nodejs의 jwt토큰 호환성 에러해결

* .

* .

* .

* update traivs

* update travis.yml

* update aws credencial

* remove directives

* update .env in private.tar.enc

* terraform add i_am_role cloudwatch for lamdbda

* update terraform.tf

* .

* .

* .

* update mutation

* update mutation

* update lambda loadbalancer

* update terrform

* .

* .

* .

* .

* before merge
@send2harishsharma
Copy link

Hi,

I have 2 rest API in springboot project

  1. /test - GET
  2. /authenticate - POST

I am able to generate token using /authenticate POST API but when I validate the token on https://jwt.io/ it says Invalid Signature

Generated Token: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmb28iLCJpYXQiOjE2MTY1MDk3NzIsImV4cCI6MTYxNjU0NTc3Mn0.DFSrZv9mDVnxWGBLP8KOQa0i8lBDJN2SHNZpGmYSsNL1-EhUXj3X1Jx0YrPuxGxwEfpBvNzs01AYKQ_DZUGkiQ

Also when I try to use this token as bearer and call /test GET API, it says status: 403, Forbidden

Here is the code snippet

// Here I am trying to remove spring security from /authenticate API but should apply on /test
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/authenticate").permitAll()
.anyRequest().authenticated();
}

private String createToken(Map<String,Object> claims, String subject)
{
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS512);

    return  Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + 1000*60*60*10))
            .signWith(key).compact();

}

Can anyone suggest what could be wrong here.

@RakshithVP
Copy link

@send2harishsharma ,
You should click on the checkbox and it works fine. I know this is a too late reply.
image

@Taraawan
Copy link

Taraawan commented Oct 4, 2022

any one work in RFC7797 using node js please guide me

@shivam-rallyhealth
Copy link

shivam-rallyhealth commented Oct 26, 2022

Doesn't work with RS256 :-( (PS: I am using Scala 2.13)

Tried all that's mentioned above:

val jwtPayload = s"""{
                        |    "exp": $time,
                        |    "iss": "$orgId",
                        |    "sub": "$technicalAccountId",
                        |    "aud": "${imsExp}/c/${clientId}",
                        |    "${imsExp}/s/${metaScope}": true
                        |}""".stripMargin
    println(jwtPayload)
    val token = Jwts.builder()
      .setPayload(jwtPayload)
      .signWith(SignatureAlgorithm.RS256,privateKey.getBytes("UTF-8"))

But this fails with:
Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.

Can someone please help?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests