node-jsonwebtoken: "JsonWebTokenError: invalid signature" when verifying JWT signed with Java JWT
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?
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 5
- Comments: 28
Links to this issue
Commits related to this issue
- jwt verify update https://github.com/auth0/node-jsonwebtoken/issues/208#issuecomment-230855763 자바와 nodejs의 jwt토큰 호환성 에러해결 — committed to lcc3108/jclip-mailserver by lcc3108 5 years ago
- add jwt and terraform setting update (#2) * . * update aws context * update .env * encrypt .env && google_key * add enviroment tar file * update apollo server context base64 encodin... — committed to lcc3108/jclip-mailserver by lcc3108 5 years ago
@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 providingsignWith()
with thebyte[]
, everything will work out.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:
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: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) asmyKey.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!
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!@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.
(Also need to catch the UnsupportedEncodingException!)
Think I would still be scratching my head next month without your help - thanks again @omsmith !
Mb
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.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.
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 byjjwt
with my favorite secretmy-secret-token-to-change-in-production
:eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImF1dGgiOiJST0xFX0FETUlOLFJPTEVfVVNFUiIsImV4cCI6MTQ2Nzc3MzQ5Nn0.v0Gfc9fKDQfkDningjmObkD5-EcbfWy5vuvuOimTV032iCoOaaQtCsZxQC78JbLbeQNLUA3UaQnuLgvwwqLmIg
Great! I can confirm that this solution works too.
@ralphgabrielle assuming the latest stable JJWT version, which is 0.10.7 at the time of writing, you can do:
@DmanDman are you signing your token just like that? You should use
new Buffer( 'ThisStringIsASecret', 'base64' )
to sign your token@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.
no problem - this was borrowed form a tutorial - can’t find the link but will credit if I find it 😄
The token is created in Java with code similar to this: