Search code examples
javabase64rsapublic-keyjava-security

Error 'Illegal base64 character' while trying convert String to PublicKey class


After generate a pair of keys with the command:

ssh-keygen -b 2048 -t rsa

I am trying use the public key (file: 'key_rsa.pub') in a java/servlet project. In the servlet class, I read the file like this:

@WebServlet(name = "Login", urlPatterns = "/login")
public class Login extends HttpServlet {
...
        InputStream publicKeyInputStream = getClass().getClassLoader().getResourceAsStream("key_rsa.pub");
        if (publicKeyInputStream == null) {
            out.println("fail - no public key");
            out.flush();
            return;
        }
        String publicKeyString = new String(publicKeyInputStream.readAllBytes(), StandardCharsets.UTF_8);
        try {
            PublicKey publicKey = util.RSAKeyGenerator.getPublicKey(publicKeyString);
...
        } catch (Exception e) {
...
        }
}

the method 'RSAKeyGenerator.getPublic' looks like that:

public class RSAKeyGenerator {
    public static final String RSA_ALGORITHM = "RSA";

    public static final int KEY_SIZE = 2048;

    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        keyPairGenerator.initialize(KEY_SIZE);
        return keyPairGenerator.generateKeyPair();
    }

    public static PublicKey getPublicKey(String publicKeyString) throws Exception {
        byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyString);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        return keyFactory.generatePublic(keySpec);
    }

    public static PrivateKey getPrivateKey(String privateKeyString) throws Exception {
        byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyString);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        return keyFactory.generatePrivate(keySpec);
    }
}

wheh I execute the application, the line 'byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyString);' in the method is giving the error 'Illegal base64 character'. What I am done wrong here?

arquivo key_rsa.pub

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCko/IRJIBoIN6fe3k8mCzsereXnQlTSwHutyMqZh7/JqbWJA31036I0YZUuOJ0d4WAMDWktWrfn7dkAlt+dqQtAlyzPUrqOc/euQ0iZPYhrd8Gn9jn+8WAQyt9L3LemgQ1ZWnXgNUk5Z1Y1vcbp8GsRWWLrxR7uvtbonU7QDJv5vEFnleObPqKeqobbg1iUoaXYkw0IAqhCTNW3ZvfWQic4FeHeNNVtT8XUrGyNPZkSgQ8QHsvSvNiLJ2bROhBD4AUZKBN/XRvNs+OSJM7agGroznHGxpwj7jZ0V+MH1HvHt95+4SnFAQJVfjza3lUPoMIOphq24V6hLzCr17lPECj kleber@kleber-desktop

Update

I follow the sugestion from @WJS below, and now my getPublicKey method looks like that:

public static PublicKey getPublicKey(String publicKeyString) throws Exception {
    publicKeyString = publicKeyString.replaceAll("ssh-rsa\\s+", "").replaceAll("\\s.*","");
    byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyString);
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
    return keyFactory.generatePublic(keySpec);
}

I think this works for remove the extra characters at the beginning and at the end of the string, but now I am getting the error java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format in the line return keyFactory.generatePublic(keySpec);


Solution

  • X509EncodedKeySpec() requires a Base64 encoded ASN.1/DER encoded public key in X.509/SPKI format, while PKCS8EncodedKeySpec() requires a Base64 encoded ASN.1/DER encoded private key in PKCS#8 format.

    Your ssh-keygen statement generated the keys in OPENSSH format (as already found in this answer), which cannot be directly processed by the posted Java code.


    You can convert the private OpenSSH key into a private PKCS#8 key as follows (note that the file will be overwritten). The converted key is unencrypted (-N "") as required by the posted Java code:

    ssh-keygen -p -N "" -m pkcs8 -f "<path to existing private OpenSSH key>" // attention: original file is overwritten
    

    Important here is the specifier -m pkcs8, which defines PKCS#8 as the target format. The specifier -m pem used in the other answer defines PKCS#1, which also cannot be (directly) processed by the posted Java code.


    The public key in X.509/SPKI format can be determined from the public OpenSSH key as follows:

    ssh-keygen -f "<path to existing public OpenSSH key>" -e -m pkcs8 > "<path to X.509 key to be generated>"
    

    or alternatively from the generated private PKCS#8 key:

    ssh-keygen -f "<path to existing private PKCS#8 key>" -i -m PKCS8 -e > "<path to X.509 key to be generated>"
    

    As an alternative to a conversion, the private key can be exported directly in PKCS#8 format:

    ssh-keygen -b 2048 -t rsa -N "" -m pkcs8 -f "<path to private PKCS#8 key to be generated>"
    

    as already posted in the other answer but with the required target format.

    Note that the public key generated this way still has the OpenSSH format and must be explicitly converted as described above.


    The generated PKCS#8 and X.509/SPKI keys are PEM encoded, i.e. they consist of header, footer and Base64 encoded body with line breaks after 64 characters. If the header, footer and line breaks are removed, the Base64 encoded ASN.1/DER encoded keys are obtained, which can be imported by the posted Java code.