RSA Encryption in Javascript and Decryption in Java

RSA Encryption in Javascript and Decryption in Java thumbnail
67K
By Dhiraj 20 February, 2020

Transmitting any sensitive information in a plain-text from any client such as Browser or Android client to server is vulnerable to security and hence encryption is a must. In this article, we will learn how to perform RSA encryption in Javascript and decrypt in Java. At the client-side, we will build a sample login form with HTML and Javascript with Angular JS and while submitting the login info to the server, we will RSA encrypt the password. At the server-side, we have  Spring MVC based app that will decrypt the encrypted password and perform authentication.

In real scenarios, RSA is not the perfect match for this kind of password encryption. RSA encryption is mostly used when there are 2 different endpoints are involved such as VPN client and server, SSH, etc. and performance-wise RSA encryption is slower. With every doubling of the RSA key length, decryption is 6-7 times slower. This use case is best fitted with AES encryption. We are creating this sample app only to demonstrate the interoperability of RSA encryption between Javascript and Java.

You can visit my previous article for AES encryption in Javascript and decryption in Java.

What is RSA Encryption

RSA is an asymmetric encryption technique that is mostly used when there are 2 different endpoints that are involved such as VPN client and server, SSH, etc. It uses two different keys as public and private keys. Here, you can encrypt sensitive information with a public key and a matching private key is used to decrypt the same.  The private key is generated in PKCS#8 format and the public key is generated in X.509 format.

I assume that you already know about RSA and here we are just implementing a use case of it. You can follow my previous article to learn RSA encryption in Java.

RSA Public and Private Key

In RSA encryption, we encrypt sensitive information with a public key and a matching private key is used to decrypt the same. Let us first generate those keys programmatically in Java. You can also use this online RSA tool to generate these keys.

We can use the factory method to generate these keys using KeyPairGenerator. For the demo purpose, we are using a key size of 1024 but this is secured and hence it is suggested to use at least 2048 bits of key size but remember that with every doubling of the RSA key length, decryption is 6-7 times slower.

By default, the private key is generated in PKCS#8 format and the public key is generated in X.509 format. Here, we are doing a BASE64 encoding of the keys so that the public key can be easily shared with any client. Once, the key is generated, we will hardcode the Base64 encoded public key in JS file and use the private key at server-side.

import java.security.*;
import java.util.Base64;

public class RSAKeyPairGenerator {

    private PrivateKey privateKey;
    private PublicKey publicKey;

    public RSAKeyPairGenerator() throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(1024);
        KeyPair pair = keyGen.generateKeyPair();
        this.privateKey = pair.getPrivate();
        this.publicKey = pair.getPublic();
    }

    public PrivateKey getPrivateKey() {
        return privateKey;
    }

    public PublicKey getPublicKey() {
        return publicKey;
    }

    public static void main(String[] args) throws NoSuchAlgorithmException {
        RSAKeyPairGenerator keyPairGenerator = new RSAKeyPairGenerator();
        System.out.println(String.format("Public Key : %s", Base64.getEncoder().encodeToString(keyPairGenerator.getPublicKey().getEncoded())));
        System.out.println(String.format("Private Key : %s", Base64.getEncoder().encodeToString(keyPairGenerator.getPrivateKey().getEncoded())));
    }
}

Below is the output. We have copied the same keys at client and server side.

rsa-public-private-keys

Below is the final project structure that we will be building. Now, let us go into the implementation:

project-structure

RSA Encryption in Javascript

Now, we have our public keys generated. We will use this public key in javascript for the RSA encryption. To perform RSA encryption at client-side, we will be using JSEncrypt. It is an open-source library to perform different encryption in Javascript.

Let us implement our HTML first. We have integrated Angular JS in it and included JS library for bootstrap and JSEncrypt.

index.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/html">
<head>
    <title>Demo</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.9/angular.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js" async></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/2.3.1/jsencrypt.min.js" async></script>

    <script src="/loginCtrl.js"></script>
</head>
<body ng-app="demoApp" ng-controller="loginController">

<div class="container">
    <div class="row content">
        <div class="col-sm-6">
            <div class="row" style="margin-top: 20px">
                <div class="col-lg-12">
                    <form>
                        <div class="form-group">
                            <label for="username">Username:</label>
                            <input type="text" class="form-control" placeholder="Username" id="username" name="username" ng-model="userName">
                        </div>
                        <div class="form-group">
                            <label for="pwd">Password:</label>
                            <input type="password" class="form-control" placeholder="Enter password" id="pwd" name="pwd" ng-model="password">
                        </div>
                        <button type="button" class="btn btn-primary" ng-click="decrypt()">Decrypt</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

Below is the Angular component implementation. It has the RSA encryption implementation in Javascript using the library JSEncrypt. We are first encrypting the plain-text passwword before sending it to server.

loginCtrl.js
var app = angular.module('demoApp', []);
app.controller('loginController', ['$scope', '$rootScope', '$http', function ($scope, $rootScope, $http) {

    $rootScope.authenticated = false;
    $scope.headingTitle = 'Login to get started.';

    let publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCz1zqQHtHvKczHh58ePiRNgOyiHEx6lZDPlvwBTaHmkNlQyyJ06SIlMU1pmGKxILjT7n06nxG7LlFVUN5MkW/jwF39/+drkHM5B0kh+hPQygFjRq81yxvLwolt+Vq7h+CTU0Z1wkFABcTeQQldZkJlTpyx0c3+jq0o47wIFjq5fwIDAQAB";

    $scope.decrypt = function(){
        if(!$scope.userName || !$scope.password){
            alert("Missing required fields.");
            return;
        }
        let RSAEncrypt = new JSEncrypt();
        RSAEncrypt.setPublicKey(publicKey);
        let encryptedPass = RSAEncrypt.encrypt($scope.password);
        const user = {
            "userName": $scope.userName,
            "password": encryptedPass
        };

        $http.post('/decrypt', user).then(
            function (response){
                alert("Decrypted Password - " + response.data.password);
        },
            function(errResponse) {
                alert(errResponse.status + ':' + errResponse.statusText);
                return $q.reject(errResponse);
            });
    };

}]);

RSA Decryption in Java

We have RSAUtil.java class implemented that handles all the RSA encryption and decryption in Java. Let us first define the controller class that handles the HTTP request.

For the demo purpose, the implementation only decrypts the password and sends it back to the client.

@Controller
public class WelcomeController {

    private static final Logger LOGGER = LoggerFactory.getLogger(WelcomeController.class);

    @Autowired
    private Environment environment;

    @RequestMapping(value={"/home"}, method = RequestMethod.GET)
    public String loginPage(){
        return "index";
    }

    @RequestMapping(value={"/decrypt"}, method = RequestMethod.POST)
    public @ResponseBody Credentials decrypt(@RequestBody Credentials credentials) throws InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException {
        String plainTextPass = RSAUtil.decrypt(credentials.getPassword(), environment.getProperty("rsa.private.key"));
        credentials.setPassword(plainTextPass);
        return credentials;
    }

}

Below is the decrypt method that accepts RSA encrypted string and Base64 encoded RSA private key for decryption.

public static String decrypt(String data, String base64PrivateKey) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
        return decrypt(Base64.getDecoder().decode(data.getBytes()), getPrivateKey(base64PrivateKey));
   }

Below is the complete implementation of RSAUtil class. It has the method defined for RSA encryption and decryption. As we discussed above the public key generated is in X.509 format and we use public key for encryption. Hence, we need X509EncodedKeySpec class to convert it again to RSA public key.

For decryption we will be using private key and we discussed above that the private key is generated in PKCS#8 format.

public class RSAUtil {

    private static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCz1zqQHtHvKczHh58ePiRNgOyiHEx6lZDPlvwBTaHmkNlQyyJ06SIlMU1pmGKxILjT7n06nxG7LlFVUN5MkW/jwF39/+drkHM5B0kh+hPQygFjRq81yxvLwolt+Vq7h+CTU0Z1wkFABcTeQQldZkJlTpyx0c3+jq0o47wIFjq5fwIDAQAB";
    private static String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALPXOpAe0e8pzMeHnx4+JE2A7KIcTHqVkM+W/AFNoeaQ2VDLInTpIiUxTWmYYrEguNPufTqfEbsuUVVQ3kyRb+PAXf3/52uQczkHSSH6E9DKAWNGrzXLG8vCiW35WruH4JNTRnXCQUAFxN5BCV1mQmVOnLHRzf6OrSjjvAgWOrl/AgMBAAECgYAgA0YHdZUFL7mmIvwuE/2+Vh7JVKRAhfM7ILNHQBx7wHkOqro9eWp8mGQhUeDvitWb1C4yizJK0Znkx/pqQtFZuoatUsggocjXFl86FElQwrBp08DvfKfd0bGgy0VTFQVmCtxiqhpAmC7xmXNZXfBD41rl9CKbFfZw05QC5BoQ0QJBAO7LSku97NgFBJQ+vbmVDonuvgnQjVNb7SnwrcpJHEUAGbaVq1a50jz+s6n39TOagASaW6pcY0uwiygYu6xDnkMCQQDAzIGNKFKomTI6djcOyHfQ1ZXqyDQ3guX6nHhzZnNHFF8ZD3fPyyIRSZ3JvPK5iEzJLhB7FRtyWkGcdXgJTWoVAkBfx9zKGqkYUJLwn2XcPWRygPdq2mMFb5bmPqqGu+KB7rNhoBD0nV4tpwALifCpPSxiLEPeRmZxoqN+dsU4KHsfAkAyQt4fK3zpAQ8MGJdf3jkGEzhC/bBHLHPB8pqgEvxIcnIcOWEVpbIa6aMd3Yk1fuftpnmbbLQ8CnWCUUlau3jFAkEAk6bOZIWhTYRwIZcwBdkpyLlbatQFoTTM3i444YutXt3FrFfaWBxge+eYKId+J4dCrt/EmHhSfWKEzHibf6N5Sg==";

    public static PublicKey getPublicKey(String base64PublicKey){
        PublicKey publicKey = null;
        try{
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(base64PublicKey.getBytes()));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            publicKey = keyFactory.generatePublic(keySpec);
            return publicKey;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return publicKey;
    }

    public static PrivateKey getPrivateKey(String base64PrivateKey){
        PrivateKey privateKey = null;
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64PrivateKey.getBytes()));
        KeyFactory keyFactory = null;
        try {
            keyFactory = KeyFactory.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        try {
            privateKey = keyFactory.generatePrivate(keySpec);
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return privateKey;
    }

    public static byte[] encrypt(String data, String publicKey) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKey));
        return cipher.doFinal(data.getBytes());
    }

    public static String decrypt(byte[] data, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(cipher.doFinal(data));
    }

    public static String decrypt(String data, String base64PrivateKey) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
        return decrypt(Base64.getDecoder().decode(data.getBytes()), getPrivateKey(base64PrivateKey));
    }

    public static void main(String[] args) throws IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, BadPaddingException {
        try {
            String encryptedString = Base64.getEncoder().encodeToString(encrypt("Dhiraj is the author", publicKey));
            System.out.println(encryptedString);
            String decryptedString = RSAUtil.decrypt(encryptedString, privateKey);
            System.out.println(decryptedString);
        } catch (NoSuchAlgorithmException e) {
            System.err.println(e.getMessage());
        }

    }
}

Testing the Encryption

Let us run our spring boot app first by running RsaJavaJavascriptApplication.java as a Java main class. Now, the index.html is accessible at localhost:8080/home. Enter the username and password and click on the button to see the decrypted password in the alert box.

rsa-encryption-decryption-result

Conclusion

In this article, we learnt about RSA encryption and how RSA encryption can be done in Javascript and the same can be decrypted in Java.

Share

If You Appreciate This, You Can Consider:

We are thankful for your never ending support.

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on Spring MVC