Search code examples
javabase64rsa

Decode Base64 with com.google.common.io.BaseEncoding -> DecodingException: Unrecognized character: -


General Goal

I want to replace the com.google.api.client.util.Base64 library with the com.google.common.io.BaseEncoding library since it is deprecated (I am working with Java 11).

I use these libraries to verify an RSA-signed jwt. To create the public key I take the String representation of the n and the e value of my certificate and turn it into a BigInteger.

Problem

My solution always throws this exception: com.google.common.io.BaseEncoding$DecodingException: Unrecognized character: -

There are - characters in the strings I want to decode, but I do not know how to work this out with the BaseEncoding Package.

Did someone encounter this problem and can help me?

Code

Here is the code with the com.google.api.client.util.Base64 decoding:

package com.example;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Calendar;

import com.auth0.jwk.InvalidPublicKeyException;
import com.auth0.jwk.JwkException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.api.client.util.Base64;


public final class App {

    public final static String PUBLIC_KEY_ALGORITHM = "RSA";

    /**
     * Returns a {@link PublicKey} if the {@code 'alg'} is {@code 'RSA'}
     *
     * @return a public key
     * @throws InvalidPublicKeyException if the key cannot be built or the key type is not RSA
     */
    public static PublicKey getPublicKey() throws InvalidPublicKeyException {
        try {
            KeyFactory kf = KeyFactory.getInstance(PUBLIC_KEY_ALGORITHM);
            byte[] decodedPubN = Base64.decodeBase64(PUB_N);
            byte[] decodedPubE = Base64.decodeBase64(PUB_E);
            BigInteger modulus = new BigInteger(1, decodedPubN);
            BigInteger exponent = new BigInteger(1, decodedPubE);
            return kf.generatePublic(new RSAPublicKeySpec(modulus, exponent));
        } catch (InvalidKeySpecException e) {
            throw new InvalidPublicKeyException("Invalid public key", e);
        } catch (NoSuchAlgorithmException e) {
            throw new InvalidPublicKeyException("Invalid algorithm to generate key", e);
        }
    }

    /**
     * 
     * 
     * @param args The arguments of the program.
     * @throws JwkException
     */
    public static void main(String[] args) throws JwkException {

        DecodedJWT jwt = JWT.decode(TOKEN);
        PublicKey publicKey = getPublicKey();
        Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null);
        algorithm.verify(jwt);
 
        if (jwt.getExpiresAt().before(Calendar.getInstance().getTime())) {
            throw new RuntimeException("Expired token!");
        }
    }
}

I refactored the getPublicKey() function with the com.google.common.io.BaseEncoding library:

/**
     * Returns a {@link PublicKey} if the {@code 'alg'} is {@code 'RSA'}
     *
     * @return a public key
     * @throws InvalidPublicKeyException if the key cannot be built or the key type is not RSA
     */
    public static PublicKey getPublicKey() throws InvalidPublicKeyException {
        try {
            KeyFactory kf = KeyFactory.getInstance(PUBLIC_KEY_ALGORITHM);
            BigInteger modulus = new BigInteger(1, BaseEncoding.base64().decode(pubN));
            BigInteger exponent = new BigInteger(1, BaseEncoding.base64().decode(pubE));
            return kf.generatePublic(new RSAPublicKeySpec(modulus, exponent));
        } catch (InvalidKeySpecException e) {
            throw new InvalidPublicKeyException("Invalid public key", e);
        } catch (NoSuchAlgorithmException e) {
            throw new InvalidPublicKeyException("Invalid algorithm to generate key", e);
        }
    }

Solution

  • Like in the comments mentioned, a JWT uses Base64Url encoding. I just had to change BaseEncoding.base64().decode(pubN) to BaseEncoding.base64Url().decode(pubN).