javajwtjjwtbase64url

JWT parsed by jjwt-java but showing invalid in jwt.io debugger


I am learning about JWS for authentication, and tried to implement using one of the java libraries jjwt. I created one JWT token string and added a single character to its end. To my surprise, jjwt library parsed it without throwing any exceptions. I don't know if there is any issue with the library or the algorithm used. I tested the same with jwt debugger and it is working as expected(showing invalid token).

CODE :

public class TestJwt {
    // private static final String JWT_SECRET_KEY = "qdsfkjbwfjn323rwefwdef3kewrwerv5236v56d56w1xweec3wdn3i432oi";          // WORKING !!!
    // private static final String JWT_SECRET_KEY = "qdsfkjbwfjn323rwefwdef3kewrwerv52f36v56d56w1xweec3wdn3i432oi";         // WORKING !!!
    // private static final String JWT_SECRET_KEY = "qdsfkjbwfjn323rwefwdef3kewrwerv52f36345345weec3wdn3i432oi";            // WORKING !!!
    // private static final String JWT_SECRET_KEY = "qdsfkjbwfjn323rwefwdef3kewrwerv52f36345432oi";                         // NOT WORKING
    // private static final String JWT_SECRET_KEY = "qdsfkjbwfjn323rwefwdef3kewrwerv52f3632222222245y6454524524tef45432oi"; // NOT WORKING
    
    private static final String JWT_SECRET_KEY = "qdsfkjbwfjn323rwefwdef3kewrwerv5236v56d56w1xweec3wdn3i432oi"; // WORKING
    private static final Key key = Keys.hmacShaKeyFor(JWT_SECRET_KEY.getBytes());
    

    public static void main(String args[]) {
        
        String token = 
        Jwts.builder()
        
        .claim("name", "RANDOM")
        .claim("surname", "ANOTHER_RANDOM")
    
        .signWith(key)
        .compact();
        
        System.out.println("TOKEN BEFORE");
        System.out.println(token);
        
        // Adding single character to jwt and still working!!
        token = token+"7";
        
        System.out.println("TOKEN AFTER");
        System.out.println(token);
        
        
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(key).build()             
                .parseClaimsJws(token).getBody();
        
        claims.forEach((k,v)->{
            System.out.println("________________________");
            System.out.println(k + " : "+v);
            
        });
        
        System.out.println("________________________"); 
    }
}

OUTPUT :

TOKEN BEFORE
eyJhbGciOiJIUzM4NCJ9.eyJuYW1lIjoiUkFORE9NIiwic3VybmFtZSI6IkFOT1RIRVJfUkFORE9NIn0.evntuAcZ0Urnv-5QniShmENKNBSzrjoxeNWN0uW-sy-qXzC-G2PJyi316m9LqQH9
TOKEN AFTER
eyJhbGciOiJIUzM4NCJ9.eyJuYW1lIjoiUkFORE9NIiwic3VybmFtZSI6IkFOT1RIRVJfUkFORE9NIn0.evntuAcZ0Urnv-5QniShmENKNBSzrjoxeNWN0uW-sy-qXzC-G2PJyi316m9LqQH97
________________________
name : RANDOM
________________________
surname : ANOTHER_RANDOM
________________________

I was expecting a SignatureException. I have tested with few keys and random claims, some of them are working but some are parsing without an issue(commented out keys). Should I use more complex keys?


Solution

  • It's not a problem with the secret, but how the Base64Url decoder was implemented.

    The original signature is 64 characters long, which in Base64Url encoding (6 bits per character) means, that there are 64 * 6 bits = 384 bits encoded. This is exactly as expected when you use the HS384 algorithm. And 384 bits/6 is exactly 64, so the signature can be encoded in a 64 character Base64Url string without any unused bits. Now when you add another character, it means that you add 6 bits. And the decoder in your Java code seems to just ignore these 6 bits, because it's not enough information for a complete byte. But technically it's an invalid Base64Url string and would require at least one more character. The decoder used on jwt.io seems to be stricter in this regard.

    See also JWT token decoding even when the last character of the signature is changed which explains why you sometimes can change the last character of the signature without invalidating it.