Carbomax
Carbomax

Reputation: 23

java - How to generate PrivateKey and PublicKey starting from a keystore (.p12)

Generate some keys with OpenSSL, then encode them in Base64 and obtain them and try to generate them to validate the authentication with JWT. Here is the code and description of what happens to me Generate with the following commands:

  openssl req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_key.der

  openssl pkcs12 -export -out keyStore.p12 -inkey private_key.pem -in public_key.der

  base64 –w 0 private_key.pem > private_key_base64_enc.txt 

  base64 –w 0 public_key.der > public_key_base64_enc.txt

I saved in my vault.keystore from wildfly: private_key_base64_enc.txt and public_key_base64_enc.txt

Then in my java class I write the following:

 private void jwtSignedAuthentication(String token, PropName vaultBlockName) throws Exception
    {


        String rsa512Alias = vaultBlockName.getDefaultValue();

        String rsa512pvt = VaultReader.getValue(rsa512Alias, "privateKey");
        String rsa512pbc = VaultReader.getValue(rsa512Alias, "publicKey");

        KeyFactory keyfatc = null;
        PrivateKey privateKey = null;
        PublicKey publicKey = null;

        try {
            keyfatc = KeyFactory.getInstance("RSA");

        } catch (NoSuchAlgorithmException e) {
             logger.error(e);
        }


        StringBuilder pkcs8Lines = new StringBuilder();
        BufferedReader rdr = new BufferedReader(new StringReader(new String(Base64.getDecoder().decode(rsa512pvt.getBytes()))));

        String line;
        while ((line = rdr.readLine()) != null) {
            pkcs8Lines.append(line);
        }

        // Remove the "BEGIN" and "END" lines, as well as any whitespace

        String pkcs8Pem = pkcs8Lines.toString();
        pkcs8Pem = pkcs8Pem.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
        pkcs8Pem = pkcs8Pem.replace("-----END ENCRYPTED PRIVATE KEY-----", "");
        pkcs8Pem = pkcs8Pem.replaceAll("\\s+","");

        byte[] dataPvt = Base64.getDecoder().decode(pkcs8Pem.getBytes());
        PKCS8EncodedKeySpec specPvt = new PKCS8EncodedKeySpec(dataPvt);

        byte[] dataPbc = Base64.getDecoder().decode(rsa512pbc.getBytes());

        StringBuilder publicLinesBuilder = new StringBuilder();
        BufferedReader readerPlubKey = new BufferedReader(new StringReader(new String(dataPbc)));

        String lineP;
        while ((lineP = readerPlubKey.readLine()) != null) {
            publicLinesBuilder.append(lineP);
        }


        String pubK = publicLinesBuilder.toString();
        pubK = pubK.replace("-----BEGIN CERTIFICATE-----", "");
        pubK = pubK.replace("-----END CERTIFICATE-----", "");
        pubK = pubK.replaceAll("\\s+","");
        X509EncodedKeySpec specPbc = new X509EncodedKeySpec(Base64.getDecoder().decode(pubK.getBytes()));

        try {

            privateKey = keyfatc.generatePrivate(specPvt);
            publicKey = keyfatc.generatePublic(specPbc);

        } catch (InvalidKeySpecException e) {
            logger.error(e);

        }

        Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) publicKey, (RSAPrivateKey) privateKey);

        // Creación de un verificador JWT
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(JWT_CLAIM_ISSUER).acceptLeeway(2).build();

        UserContext userContext = new UserContext();
        userContext.setUserName(JWT_CLAIM_ISSUER);

        try {
            // Decode JWT, verificación del token.
            @SuppressWarnings("unused")
            DecodedJWT decodeJwt = verifier.verify(token);

        } catch (JWTDecodeException e) {
        logger.error(e);
        }

    }

When I try to generate the keys I return null:

privateKey = keyfatc.generatePrivate(specPvt);
publicKey = keyfatc.generatePublic(specPbc);

Anyone have any idea what happens with this. Thanks in advance

For generate my JWT:

 public ResteasyWebTarget getClientWebAgent(String host, String blockName) throws KeyStoreException
    {
        ResteasyClient clientBuilder = new ResteasyClientBuilder().establishConnectionTimeout(10, TimeUnit.SECONDS).socketTimeout(5, TimeUnit.SECONDS).build();
        ResteasyWebTarget target = clientBuilder.target(host);
        KeyPair keys = null;
        try {
            keys = keyStore.getKeys();
            /*logger.infov(new String(Base64.getEncoder().encode(keys.getPrivate().getEncoded())));
            logger.infov("****PUBLIC KEY ******");
            logger.infov(new String(keys.getPublic().getEncoded()));*/
        } catch (IOException e) {
            logger.error(e);
        }
          Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) keys.getPublic(), (RSAPrivateKey) keys.getPrivate());
          Map<String, Object> headerClaims = new HashMap<>();
          headerClaims.put("alg", "RS512");
          headerClaims.put("typ", "JWT");

          JWTCreator.Builder jwtCreator = JWT.create();
          jwtCreator.withHeader(headerClaims);
          jwtCreator.withIssuer(JWT_CLAIM_ISSUER);
          jwtCreator.withIssuedAt(LocalDate.now().toDate());
          jwtCreator.withExpiresAt(LocalDate.now().toDateTimeAtCurrentTime().plusSeconds(30).toDate());
          String jwtToken = jwtCreator.sign(algorithm);
          target.register(new BearerAuthenticator(jwtToken));
          target.register(new LanguageHeaderToken(Locale.getDefault()));

        return target;
    }

Upvotes: 2

Views: 4919

Answers (2)

dave_thompson_085
dave_thompson_085

Reputation: 38821

Your 'public key' is actually a certificate (specifically an X.509 v1 or v3 certificate, depending on your openssl config), which contains a publickey but is different from a publickey -- and is in PEM format even though you have misleadingly named it .der -- and your privatekey is encrypted.

In addition to the approach of using a PKCS12, as Roberto validly proposes and is usually the simplest because it's only one file to manage and is still encrypted and thus more secure:

  • Java can handle an X.509 certificate, but you use a CertificateFactory.getInstance("X.509") and give it an InputStream instead of a KeyFactory and an X509EncodedKeySpec. CertificateFactory can handle either PEM or DER, unlike KeyFactory which can handle only DER, so you don't need the de-PEM (strip BEGIN/END/EOL and decode base64) parts.

  • standard Java cannot handle encrypted PKCS8 keys directly. If you can add a thirdparty library, BouncyCastle's bcpkix can do so; search the dozen or so existing Qs that use PEMParser (not PEMReader, that's the older version) and JceOpenSSLPKCS8DecryptorBuilder. Otherwise, you can add -nodes to your req -newkey -x509 command to generate an unencrypted privatekey file, which after you de-PEM it does work in KeyFactory with PKCS8EncodedKeySpec. (It's still spelled -nodes even though the encryption used without it hasn't been plain aka single DES for decades.) Using an unencrypted privatekey file of course means that any intruder or malware on your system that can read that file can get your privatekey, which in many situations is a risk.

  • finally, if you really want only the keypair and not a certificate, don't bother with req -newkey -x509. Instead use openssl genpkey to generate the privatekey, or the older but simpler openssl genrsa -nodes followed by (or piped to) openssl pkcs8 -topk8 -nocrypt to convert it to PKCS8-unencrypted format. Then use openssl pkey -pubout or the older openssl rsa -pubout to make a separate file with the publickey. Those commands can write (and read back where applicable) DER format instead of PEM; if you do that, your code doesn't need the de-PEM steps, you can just pass the binary file contents to KeyFactory. The risks for an unencrypted file are the same as above.

Upvotes: 2

Roberto Manfreda
Roberto Manfreda

Reputation: 2623

Maybe you are generating the keystore without assigning a valid alias, looking at your command you are not using the -name option.
The command should be like this:

openssl pkcs12 -export -out keyStore.p12 -inkey private_key.pem -in public_key.der -name "alias"  

A smarter way to use the keys in java is by creating a KeyPair:

KeyPair loadKeyPair() throws Exception {
    // Read keystore from resource folder
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL resource = classLoader.getResource("keyStore.p12");
    File file = new File(Objects.requireNonNull(resource).toURI());

    char[] keyPass = "1234".toCharArray();
    String alias = "alias";

    KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
    try (FileInputStream is = new FileInputStream(file)) {
        keystore.load(is, keyPass);
    }

    Key key = keystore.getKey(alias, keyPass);
    if (key instanceof PrivateKey) {
        // Get certificate of public key
        Certificate cert = keystore.getCertificate(alias);

        // Get public key
        PublicKey publicKey = cert.getPublicKey();

        // Return a key pair
        return new KeyPair(publicKey, (PrivateKey) key);
    }

    return null;
}

Then extract RSAPublicKey and RSAPrivateKey keys from the KeyPair:

void loadKeys() throws Exception{
    KeyPair keyPair = loadKeyPair();

    if (null != keyPair) {
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
    }
}

Hope it can be helpful and good luck with your Json Web Tokens! :-p

Upvotes: 2

Related Questions