jwtk / jjwt

Java JWT: JSON Web Token for Java and Android
Apache License 2.0
10.31k stars 1.34k forks source link

HS256 token generated using previous version 0.9 is not compatible with 0.11 #613

Closed gauravk48 closed 1 year ago

gauravk48 commented 4 years ago

We have a server which issues JWT token using HS256 algorithm and some secret using Jwtt version 0.9. The client has upgraded to version 0.11 and now even while using the same secret to validate, its not working. When we just change the version to 0.9, the code works.

Here is the 0.9 code:

Jws claims = Jwts.parser() .requireIssuer(ISSUER) .setSigningKey(SECRET) .parseClaimsJws(jwt);

Here is the 0.11 code:

Jws claims = Jwts.parserBuilder() .requireIssuer(ISSUER) .setSigningKey(Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET))) .build() .parseClaimsJws(token);

lhazlewood commented 4 years ago

Without seeing more, it looks like perhaps you might be hitting key length assertions introduced in 0.10.0?

If you you run just the following:

Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET))

do you get a WeakKeyException ?

If you do, you should use a stronger key on the server since the one currently being used isn't strong enough for the desired algorithm. If you cannot change that key on the server, you cannot upgrade to JJWT 0.10.0 or later on the client because of the security assertions - at least until you upgrade the server key.

If you are not seeing a WeakKeyException, please do this:

  1. Create a new test JWT that uses your server code with JJWT 0.9. Use a different test secret value (instead of what you have for SECRET currently) that is the exact same length (number of bytes) as your current SECRET value.
  2. Paste that jwt and that test secret value here (again, ensure this is a test secret - not something you would actually ever use in your application to ensure you can share it with us for testing purposes).

Once we have both of those, we can try to recreate and diagnose the problem on 0.11.x. I'm suspecting it's a key length issue though. Let us know!

(Also, for future questions, please kindly do not use GitHub Issues to ask usage questions. Please read https://github.com/jwtk/jjwt#questions Thanks 😃 )

lhazlewood commented 4 years ago

Any update on this?

gauravk48 commented 4 years ago

I will check in some code to reproduce the issue.

Thanks

On Wed, Aug 5, 2020 at 9:44 AM Les Hazlewood notifications@github.com wrote:

Any update on this?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/jwtk/jjwt/issues/613#issuecomment-669303915, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGXKZOXO5L3SQFGUFT2UIRTR7GD7HANCNFSM4PMMDJNA .

gauravk48 commented 4 years ago

I have created two repositories with code to show that the token generated by one version is not compatible with the other.

Here is the jwt 0.9 repo: https://github.com/gauravk48/jwttest9 Here is the jwt 0.11 repo: https://github.com/gauravk48/jwttest11

You can compile using maven, and then generate the token from one version and try using it on the later version. It shows the following error: Test jwt token, current time: Tue Aug 18 11:17:11 PDT 2020 Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.simontuffs.onejar.Boot.run(Boot.java:340) at com.simontuffs.onejar.Boot.main(Boot.java:166) Caused by: io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted. at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:354) at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481) at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541) at gauravk48.JwtTest.validateTokenAndGetSubj(JwtTest.java:51) at gauravk48.JwtTest.main(JwtTest.java:24) ... 6 more

gauravk48 commented 4 years ago

@lhazlewood Please take a look and suggest how to resolve this. The token is generated on one host but should be able to validate on any different host as long as the secret is shared between them.

lhazlewood commented 4 years ago

@gauravk48 thanks so much for putting this together! I'll take a look as soon as I can - probably later today or tomorrow.

gauravk48 commented 4 years ago

Any updates on this @lhazlewood Thanks for looking.

lhazlewood commented 4 years ago

@gauravk48 sorry for the delay - I was deep in JWE code all week! I hope I can get to this soon.

gauravk48 commented 4 years ago

Just checking if you got time to look into the issue> @lhazlewood

lhazlewood commented 4 years ago

@gauravk48 not yet - I had other responsibilities that prevented me from digging in further. I should be able to get back to this soon - probably this or next week. Thanks for the reminder!

stale[bot] commented 3 years ago

This issue has been automatically marked as stale due to inactivity for 60 or more days. It will be closed in 7 days if no further activity occurs.

gauravk48 commented 3 years ago

@lhazlewood Any update on this?

slovenec88 commented 3 years ago

We kinda had a similar issue.

It was between the server on 0.9, and the client with a different jwt library (njwt latest). To fix it we had to increase the secret above 265 bytes and base64 encode the secret on the client side.

lhazlewood commented 3 years ago

@bdemers if you get a sec would you mind taking a look at this when you get a sec? I'm unable to pick up new work until the JWE stuff is done (I'm in the thick of it).

stale[bot] commented 2 years ago

This issue has been automatically marked as stale due to inactivity for 60 or more days. It will be closed in 7 days if no further activity occurs.

bmarwell commented 2 years ago

Ping. I'd like to help out.

sruffatti commented 1 year ago

I encountered a WeakKeyException. I wanted to share how we are temporarily solving this. We are trying to figure out how to role this change out across many microservices. We may have microservices using JJWT 0.11, but our security microservice generating JWTs using 0.9.

Our secret was only 384 bits, leading to a WeakKeyException. So we are padding the existing secret to meet the 512 bit requirement. byte[] paddedKey = shortKey.length() < 64 ? Arrays.copy(shortKey, 64); : shortKey;

This will allow a JWT generated using the shortKey to be parsed by microservices using 0.11 by padding the shortKey.

lhazlewood commented 1 year ago

Closing this as represented by @sruffatti 's workaround, as well as a potential solution in the upcoming 0.12.0: custom SignatureAlgorithm implementations are now supported, and if you need validation that is different than the RFC requirements, you can implement your own SignatureAlgorithm and plug that in via JwtParserBuilder.addSignatureAlgorithms

SushSpaceBasic commented 3 months ago

I have a Spring Boot 2.2 with JJWT 0.9.1 project that generates the JWT tokens of length 32 bits and this token is being used in another Spring Boot project in which I'm trying to upgrade to Spring Boot 3 and JJWT 0.12.6. I'm trying to follow @sruffatti's approach of padding the secret key but it is throwing this exception:

Request to parse JWT with invalid signature : failed : {}
io.jsonwebtoken.security.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted. 

This is my Java code:

private final String SECRET_KEY = "MY_SECRET_KEY"; // 32 bits
private final byte[] paddedSecretKey = Arrays.copyOf(MY_SECRET_KEY.getBytes(StandardCharsets.UTF_8), 32); // 256 bits

private Claims extractAllClaims(String token) {
    return Jwts.parser().setSigningKey(paddedSecretKey).build().parseClaimsJws(token).getBody();
}

Any help would be appreciated

lhazlewood commented 3 months ago

@SushSpaceBasic .setSigningKey has been deprecated as of 0.12.0, use verifyWith using a key object directly. You could obtain one using:

byte[] paddedSecretKeyBytes = shortKey.length() < 64 ? Arrays.copy(shortKey, 64); : shortKey;
SecretKey secretKey = new SecretKeySpec(paddedSecretKeyBytes);
Jwts.parser().verifyWith(secretKey)// ... etc ...

BUT NOTE:

@sruffatti's fix (using padding as shown above) only works if both signature creation and signature verification uses the same exact padding and resulting key bytes - you can't just add padding to the key byte array for verification only. The issuer (the entity that creates the JWT and its signature) must also pad the key byte array in the exact same way. Otherwise they would technically be separate cryptographic keys (not symmetric) and thus incompatible.

Finally, note that padding should only ever be thought of as a 'quick fix', and ideally, not even used. A safe uniformly-distributed (i.e. indistinguishably random) cryptographic key is the only safe way. Padding with zeros ensures that the padded part is distinctly non-random, which breaks the keys strength.

SushSpaceBasic commented 3 months ago

Thanks for the quick clarification @lhazlewood. Really appreciate it