JavaSecurity

[Java] Signing and validating data using JKS and public key

In this article I will show how to sign string data with private key and validate it using public key.

All code examples you can find here.

First thing you have to do is generate public and private key and save it as Java Key Store. To do it, I will use KeyStore Explorer which you can download from here.

Click on File -> New and choose PKCS #12 KeyStore

Now choose Tools -> Generate Key Pair and select default settings.

Click on Edit name (book icon) and define the Common Name (CN). I will put “Test”.

Enter alias for example “Test” and click OK button.

Now you need to define password. I will enter “Test”.

Your key pair should be generated:

Now click to File -> Save as and enter password to KeyStore. I will put “Test”

Save the KeyStore with “jks” extension to some location.

Now your KeyStore with private and public key is ready. We can start coding.

Firstly I will create dedicated method to read our KeyStore

 private static KeyStore getKeyStore(String path, String password) throws Exception {
        FileInputStream keyStoreStream = new FileInputStream(path);
        KeyStore keyStore = KeyStore.getInstance("jks");
        keyStore.load(keyStoreStream, password.toCharArray());
        return keyStore;
    }

Now we can implement signing data method.

public static String signData(String text, String keyStorePath, String keyStorePassword) throws Exception {
        var keyStore = getKeyStore(keyStorePath, keyStorePassword);
        var privateKey = (PrivateKey) keyStore.getKey("test", "Test".toCharArray());

        var dataToValidate = text.getBytes("UTF8");

        var sig = Signature.getInstance("SHA512WithRSA");
        sig.initSign(privateKey);
        sig.update(dataToValidate);
        var signatureBytes = sig.sign();
        var signature = Base64.encodeBase64String(signatureBytes);
        return signature;
    }

For signing data we need to retrieve private key from KeyStore, initialize signature, add data to sign and sign it. We convert signature to Base64, to be able serialize it to text.

Method to verify data is very similar, but we use public key instead private.

public static boolean verifyData(String text, String signature, String keyStorePath, String keyStorePassword) throws Exception {
        var keyStore = getKeyStore(keyStorePath, keyStorePassword);
        var publicKey = keyStore.getCertificate("test").getPublicKey();
        var sig = Signature.getInstance("SHA512WithRSA");

        var dataToValidate = text.getBytes("UTF8");

        sig.initVerify(publicKey);
        sig.update(dataToValidate);
        return sig.verify(Base64.decodeBase64(signature));
    }

Finally we can make some test, to be sure, that our code work properly.

    @org.junit.jupiter.api.Test
    public void GivenPhrase_TextTheSame_ThenGoodValidation() throws Exception {
        var jksPath = System.getProperty("user.dir") + "\\src\\KeyStore.jks";
        var signature = Crypto.signData("Text to validate", jksPath, "Test");
        var resultCheckGood = Crypto.verifyData("Text to validate", signature, jksPath, "Test");
        assertTrue(resultCheckGood);
    }

    @org.junit.jupiter.api.Test
    public void GivenPhrase_TextTheSame_ThenBadValidation() throws Exception {
        var jksPath = System.getProperty("user.dir") + "\\src\\KeyStore.jks";
        var signature = Crypto.signData("Text to validate", jksPath, "Test");
        var resultCheckBad = Crypto.verifyData("Other text to validate", signature, jksPath, "Test");
        assertFalse(resultCheckBad);
    }

If we cannot have KeyStore, but only public key we can still validating data.

Lets export public key from our KeyStore.

Now we can verify our signature like this

public static boolean verifyDataWitPublicKey(String text, String signature, String publicKeyPath) throws Exception {
        var key = new String(Files.readAllBytes(new File(publicKeyPath).toPath()));
        var publicKeyPem = key
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replaceAll(System.lineSeparator(), "")
                .replace("-----END PUBLIC KEY-----", "");

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyPem));
        var publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);

        var sig = Signature.getInstance("SHA512WithRSA");

        var dataToValidate = text.getBytes("UTF8");

        sig.initVerify(publicKey);
        sig.update(dataToValidate);
        return sig.verify(Base64.decodeBase64(signature));
    }

And let’s make a test to check is it works.

 @org.junit.jupiter.api.Test
    public void GivenPhrase_TextTheSame_ThenGoodValidationForPublicKey() throws Exception {
        var jksPath = System.getProperty("user.dir") + "\\src\\KeyStore.jks";
        var publicKeyPath = System.getProperty("user.dir") + "\\src\\publicKey.pem";
        var signature = Crypto.signData("Text to validate", jksPath, "Test");
        var resultCheckGood = Crypto.verifyDataWitPublicKey("Text to validate", signature, publicKeyPath);
        assertTrue(resultCheckGood);
    }

Leave a Reply