Capitano Giovarco
Capitano Giovarco

Reputation: 205

My HMACSHA256 signature validation almost works but not quite

I wrote a method that takes a JWT as a request and checks if the signature is valid.

This is the unit test:

@Test
public void isValid() {
    final JwtValidator jwtValidator = JwtValidator.getInstance();
    final boolean valid = jwtValidator.isValid("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
    Assert.isTrue(valid);
}

and this is the code:

@SneakyThrows
public boolean isValid(String extractedToken) {
    final String[] tokenParts = extractedToken.split(Pattern.quote("."));
    String header = tokenParts[0];
    String payload = tokenParts[1];
    String signature = tokenParts[2];

    final byte[] calcHmacSha256 = HMAC.calcHmacSha256("your-256-bit-secret".getBytes(), (header+"."+payload).getBytes());

    final String s = Base64.getEncoder().encodeToString(calcHmacSha256);

    System.out.println("'" + signature + "'.equals('"+s+"')");
    return signature.equals(s);
}

The log prints two strings that differ only for 2 chars, so I feel like I'm close "but not quite" to make it work:

'SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.equals('SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV/adQssw5c=')

There are of course hard coded values because the implementation isn't complete, but I'm using the example values in https://jwt.io/ for ease of use right now.

Thanks!

EDIT 1:

public class JwtValidatorTest {

    @Test
    public void isValid() {
        byte[] header64 = Base64.getEncoder().encode("{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes());
        byte[] payload64 = Base64.getEncoder().encode("{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}".getBytes());

        final byte[] calcHmacSha256 = HMAC.calcHmacSha256("your-256-bit-secret".getBytes(), (header64+"."+payload64).getBytes());
        final String signature64 = Base64.getEncoder().encodeToString(calcHmacSha256);

        final String input = header64 + "." + payload64 + "." + signature64;

        final JwtValidator jwtValidator = JwtValidator.getInstance();

        final boolean valid = jwtValidator.isValid(input);
        
        Assert.isTrue(valid);
    }
}

Upvotes: 1

Views: 1068

Answers (1)

jps
jps

Reputation: 22555

The difference is just caused by the different encoding used here. You used Base64 encoding, but the original signature is Base64Url encoded. Base64Url encoding is, according to RFC7519, the standard encoding for JWT:

Each part contains a base64url-encoded value

Base64Url encoding has no padding (=) on the end and the characters + and / are replaced with -and _.

This code should solve the problem:

final String s = Base64.getUrlEncoder().withoutPadding().encodeToString(calcHmacSha256);

Upvotes: 5

Related Questions