4

I'm receiving a JWT and would like to verify it's signature. It's not encrypted, is based64 encoded and is signed using HmacSha256. It is signed with a secret that I know.

I can't seem to find any example of how to verify the signature without using third part libraries listed on https://jwt.io/ i.e. java-jwt, jpose4j, etc....

Is it possible to do this?

What I have so far:

private boolean validateSignature( String header, String data, String signature, String secretKey ) throws Exception {
    Base64 base64 = new Base64( true );
    SecretKeySpec secret = new SecretKeySpec( secretKey.getBytes(), "HmacSHA256" );
    Mac mac = Mac.getInstance( "HmacSHA256" );
    mac.init( secret );

    byte[] hmacDataBytes = mac.doFinal( data.getBytes( StandardCharsets.UTF_8.name()) );
    String hmacData = new String( hmacDataBytes );

    return hmacData.equals( signature ); // Compare signatures here...
}

Based on @pedrofb and @jps answers, here is solution:

private boolean validToken( String authToken, String key ) throws Exception {
    String[] token = authToken.split( "\\." );
    String header = token[0];
    String payload = token[1];
    String originalSignature = token[2];

    SecretKeySpec secret = new SecretKeySpec( Base64.getDecoder().decode( key ), ALGORITM_HMACSHA256 );
    Mac mac = Mac.getInstance( ALGORITM_HMACSHA256 );
    mac.init( secret );

    StringBuilder headerAndPayload = new StringBuilder( header ).append( "." ).append( payload );

    byte[] signatureBytes = mac.doFinal( headerAndPayload.toString().getBytes( StandardCharsets.UTF_8.name() ) );
    String calculatedSignature = Base64.getUrlEncoder().withoutPadding().encodeToString( signatureBytes );

    return calculatedSignature.equals( originalSignature );
}
Thomas Buckley
  • 5,836
  • 19
  • 62
  • 110

2 Answers2

4

A JWT have three parts encoded in base64url separated by dots

header.payload.signature

The signature is calculated over header.payload

Assuming that your method receives the elements in base64url, you need to calculate HMAC on header + "." + data, encode the result to base64url, and compare with the signature field

Something like this:

private boolean validateSignature( String header, String data, String signature, String secretKey ) throws Exception {

    SecretKeySpec secret = new SecretKeySpec( secretKey.getBytes(), "HmacSHA256" );
    Mac mac = Mac.getInstance( "HmacSHA256" );
    mac.init( secret );

    String body = header + "." + data;
    byte[] hmacDataBytes = mac.doFinal( body.getBytes( StandardCharsets.UTF_8.name()) );
    String hmacData = Base64.getUrlEncoder().encodeToString( hmacDataBytes );

    return hmacData.equals( signature ); // Compare signatures here...
}
pedrofb
  • 37,271
  • 5
  • 94
  • 142
  • Thanks Pedro. Have tried that but hmac is still not equals original signature. On Jwt.io - when I enter header, body and signature into Encoded field input it also tells me the signature is invalid. If I select the checkbox 'secret base64 encoded' in the Verfify Signature container, it then auto-updates the signature part in decoded input field and says the signature is verified! Any ideas what I might be doing wrong? – Thomas Buckley Jul 11 '18 at 09:27
  • @ThomasBuckley can you show your token and secret (of course only examples for illustration, not real production ones) It's easier if we see what you're talking about. – jps Jul 11 '18 at 09:33
  • Is your secret key coded in base64? Then replace `secretKey.getBytes()` with `Base64.getDecoder().decode(secretKey)`. If it does not work, post an example token&secret key here as suggest @jps. jwt.io is not very intuitive to verify. First insert the key and second paste the token. – pedrofb Jul 11 '18 at 13:09
  • Yes, it seems the secret was base64 coded also. Replaced with your suggested code and calculated signature is j4jBeXXXXXXJBl-uDXX-dcKzr4O8c3CXXXXXgxKW5WA= and stripped token signature is j4jBeXXXXXXJBl-uDXX-dcKzr4O8c3CXXXXXgxKW5WA. So I used Base64.getUrlEncoder().withoutPadding() to get calculated signature. Thanks Predo and jps – Thomas Buckley Jul 12 '18 at 08:08
3

To verify the signature, you need to take the Base64Url encoded header and payload, calculate the HMACSha256 hash with your secret, Base64Url encode the result and compare it to the original signature. You need of course libs for the HMACSha256 algorithm and Base64#Url encoding, but no specific JWT libs.

Written in pseudo code to show the principle:

hash = HmacSHA256(header + "." + payload , secret)

here header is the Base64Url encoded header, payload is the Base64Url encoded payload

result = Base64UrlEncode(hash)

resultcan now be compared to the original signature.

In another answer I described the process with nodes.js and online tools.

jps
  • 20,041
  • 15
  • 75
  • 79